├── suite ├── dtype │ ├── __init__.py │ ├── container.py │ └── primitive.py ├── io │ ├── __init__.py │ ├── icontainer.py │ └── iprimitive.py ├── __init__.py ├── renderer.py ├── idchecker.py ├── structure.py ├── projectMaker.py └── validator.py ├── docs ├── validators.rst └── specs.rst ├── tests ├── assets │ ├── entity1.json │ ├── link1.json │ ├── simple3.json │ ├── simple1.json │ ├── combined1.json │ ├── predicate1.json │ └── simple2.json ├── testrunner.py ├── test_container.py ├── test_structure.py ├── test_primitive.py ├── test_io_primitive.py ├── test_io_container.py └── test_edition.py ├── setup.py ├── .travis.yml ├── packages-spec.txt ├── .gitignore ├── README.rst └── LICENSE /suite/dtype/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /suite/io/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /suite/__init__.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | -------------------------------------------------------------------------------- /suite/renderer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains renderers for structures 3 | that are used for documents used by the project 4 | """ 5 | # author: Kaan Eraslan 6 | # license: see, LICENSE 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/validators.rst: -------------------------------------------------------------------------------- 1 | ########### 2 | Validators 3 | ########### 4 | 5 | Each document specified in the `specs `_ has an associated 6 | validator, which checks whether a given projects assets conform with 7 | specs. 8 | -------------------------------------------------------------------------------- /tests/assets/entity1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sample-entity-1": { 3 | "sample-relation-1": { 4 | "0": "sample-word-2" 5 | } 6 | }, 7 | "sample-entity-2": { 8 | "sample-relation-2": { 9 | "0": "sample-entity-1" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/assets/link1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sample-entity-1": { 3 | "sample-relation-3": { 4 | "0": "sample-predicate-1" 5 | } 6 | }, 7 | "sample-entity-2": { 8 | "sample-relation-3": { 9 | "0": "sample-predicate-2" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/assets/simple3.json: -------------------------------------------------------------------------------- 1 | { 2 | "sample-relation-1": { 3 | "equals": "" 4 | }, 5 | "sample-relation-2": { 6 | "contains": "" 7 | }, 8 | "sample-relation-3": { 9 | "defined as": "" 10 | }, 11 | "sample-relation-4": { 12 | "is not": "" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/testrunner.py: -------------------------------------------------------------------------------- 1 | # test runner file 2 | import unittest 3 | import os 4 | 5 | if __name__ == "__main__": 6 | loader = unittest.TestLoader() 7 | start_dir = os.path.join(os.path.curdir, "tests") 8 | suite = loader.discover(start_dir) 9 | runner = unittest.TextTestRunner() 10 | runner.run(suite) 11 | -------------------------------------------------------------------------------- /tests/assets/simple1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sample-word-1": { 3 | "lorem": "" 4 | }, 5 | "sample-word-2": { 6 | "ipsum": "" 7 | }, 8 | "sample-word-3": { 9 | "dolor": "" 10 | }, 11 | "sample-word-4": { 12 | "sit": "" 13 | }, 14 | "sample-word-5": { 15 | "amet": "" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/assets/combined1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sample-combined-grammar-1": { 3 | "coordinating conjunction": "", 4 | "sample-relation-2": { 5 | "0": "sample-grammar-1" 6 | } 7 | }, 8 | "sample-combined-grammar-2": { 9 | "feminine substantif": "", 10 | "sample-relation-2": { 11 | "0": "sample-grammar-6", 12 | "1": "sample-grammar-2" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/assets/predicate1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sample-predicate-1": { 3 | "sample-relation-3": { 4 | "0": "sample-combined-grammar-2", 5 | "1": "sample-grammar-5", 6 | "2": "sample-grammar-7" 7 | }, 8 | "sample-relation-1": { 9 | "0": "sample-word-3" 10 | } 11 | }, 12 | "sample-predicate-2": { 13 | "sample-relation-3": { 14 | "0": "sample-combined-grammar-2" 15 | }, 16 | "sample-relation-1": { 17 | "0": "sample-predicate-1" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/assets/simple2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sample-grammar-1": { 3 | "conjunction": "" 4 | }, 5 | "sample-grammar-2": { 6 | "substantif": "" 7 | }, 8 | "sample-grammar-3": { 9 | "verb": "" 10 | }, 11 | "sample-grammar-4": { 12 | "subject": "" 13 | }, 14 | "sample-grammar-5": { 15 | "nominative": "" 16 | }, 17 | "sample-grammar-6": { 18 | "feminine": "" 19 | }, 20 | "sample-grammar-7": { 21 | "singular": "" 22 | }, 23 | "sample-grammar-8": { 24 | "plural": "" 25 | }, 26 | "sample-grammar-9": { 27 | "dual": "" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools 3 | 4 | 5 | # currentdir = os.getcwd() 6 | 7 | with open("README.rst", "r", encoding="utf-8") as f: 8 | long_desc = f.read() 9 | 10 | with open("LICENSE", "r", encoding="utf-8") as f: 11 | license_str = f.read() 12 | 13 | setuptools.setup( 14 | name="edigital", 15 | version="0.1", 16 | author='Kaan Eraslan', 17 | python_requires='>=3.5.0', 18 | author_email="kaaneraslan@gmail.com", 19 | description="Digital Edition Suite using Authority Documents", 20 | long_description=long_desc, 21 | long_description_content_type="text/markdown", 22 | license=license_str, 23 | url="https://github.com/D-K-E/digital-edition-suite", 24 | packages=setuptools.find_packages( 25 | exclude=["tests", "*.tests", "*.tests.*", "tests.*", 26 | "docs", ".gitignore", "README.rst"] 27 | ), 28 | test_suite="tests", 29 | install_requires=[], 30 | classifiers=[ 31 | "Programming Language :: Python :: 3", 32 | "License :: Creative Commons Attribution 4.0 International", 33 | "Operating System :: OS Independent", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.5" 5 | - "3.6" 6 | - "3.7" 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | install: 13 | - sudo apt-get update 14 | # We do this conditionally because it saves us some downloading if the 15 | # version is the same. 16 | - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then 17 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; 18 | else 19 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 20 | fi 21 | - bash miniconda.sh -b -p $HOME/miniconda 22 | - export PATH="$HOME/miniconda/bin:$PATH" 23 | - hash -r 24 | - conda config --set always_yes yes --set changeps1 no 25 | - conda update -q conda 26 | # Useful for debugging any issues with conda 27 | - conda info -a 28 | 29 | # Replace dep1 dep2 ... with your dependencies 30 | - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION --file 31 | packages-spec.txt 32 | - source activate test-environment 33 | # build package structure 34 | - pip install . 35 | 36 | script: 37 | - python tests/test_container.py 38 | - python tests/test_structure.py 39 | - python tests/test_primitive.py 40 | - python tests/test_io_primitive.py 41 | - python tests/test_io_container.py 42 | -------------------------------------------------------------------------------- /packages-spec.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: linux-64 4 | @EXPLICIT 5 | https://repo.anaconda.com/pkgs/main/linux-64/_libgcc_mutex-0.1-main.conda 6 | https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2019.9.11-hecc5488_0.tar.bz2 7 | https://repo.anaconda.com/pkgs/main/linux-64/libstdcxx-ng-9.1.0-hdf63c60_0.conda 8 | https://repo.anaconda.com/pkgs/main/linux-64/libgcc-ng-9.1.0-hdf63c60_0.conda 9 | https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h516909a_1.tar.bz2 10 | https://conda.anaconda.org/conda-forge/linux-64/libffi-3.2.1-he1b5a44_1006.tar.bz2 11 | https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.1-hf484d3e_1002.tar.bz2 12 | https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1c-h516909a_0.tar.bz2 13 | https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.4-h14c3975_1001.tar.bz2 14 | https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h516909a_1006.tar.bz2 15 | https://conda.anaconda.org/conda-forge/linux-64/readline-8.0-hf8c457e_0.tar.bz2 16 | https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.9-hed695b0_1003.tar.bz2 17 | https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.30.1-hcee41ef_0.tar.bz2 18 | https://conda.anaconda.org/conda-forge/linux-64/python-3.7.3-h33d41f4_1.tar.bz2 19 | https://conda.anaconda.org/conda-forge/linux-64/certifi-2019.9.11-py37_0.tar.bz2 20 | https://conda.anaconda.org/conda-forge/linux-64/setuptools-41.4.0-py37_0.tar.bz2 21 | https://conda.anaconda.org/conda-forge/linux-64/wheel-0.33.6-py37_0.tar.bz2 22 | https://conda.anaconda.org/conda-forge/linux-64/pip-19.3.1-py37_0.tar.bz2 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # vim project settings 107 | .vim/** 108 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ###################### 2 | digital-edition-suite 3 | ###################### 4 | 5 | .. image:: https://travis-ci.com/D-K-E/digital-edition-suite.svg?branch=master 6 | :target: https://travis-ci.com/D-K-E/digital-edition-suite 7 | 8 | Small scripts to publish digital editions using authority files easier. 9 | This is intended as small collection of scripts that are not necessarily scalable, 10 | but that help to produce machine oriented data during edition projects. 11 | 12 | It binds the modification of authority files and related structures to a workflow, 13 | which in turn should significantly cut back schema related errors. 14 | 15 | It is not done yet 16 | 17 | Use cases 18 | ========== 19 | 20 | New Project 21 | ------------ 22 | 23 | Let's say you want to start a project from scratch. How should you proceed ? 24 | Here are the steps: 25 | 26 | 1. You define a relations file, and associate function representation (binary 27 | or string) with each relation. 28 | 29 | 2. You start to write your document in html or in some templating language. 30 | 31 | 32 | 3. Label your entities. For easy generation of authority documents label unit 33 | entities first. Then generate a simple authority file from them. This would 34 | come in handy afterwards. 35 | 36 | 4. Before going on further, you should create all the authority files 37 | necessary for creating a predicate document. Once all of those are created, 38 | you can start creating predicates that are going to be associated with 39 | entities. 40 | 41 | 5. For each entity there must be a unique predicate for a given relation. At 42 | this point you should see whether there are any entities that can not be 43 | associated with any of predicates. 44 | 45 | 6. Link predicate inside a predicate document with an entity. 46 | 47 | 7. Use either the corresponding command line or graphical interface for 48 | handling all the document types mentioned above. 49 | 50 | Already Existing Project 51 | -------------------------- 52 | 53 | 54 | Exporting Project Data 55 | ---------------------- 56 | 57 | The suite gives you two choice for serializing your data: :code:`json` and 58 | :code:`xml`. Once you have one of these data you can virtually recreate a 59 | project in another environment. 60 | 61 | -------------------------------------------------------------------------------- /suite/idchecker.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: check ids for given project assets 4 | # no duplicate should be involved in any of the keys 5 | 6 | import os 7 | import json 8 | import glob 9 | import sys 10 | 11 | 12 | def read_json(jsonpath: str) -> dict: 13 | "read json object from given path" 14 | assert os.path.isfile(jsonpath) 15 | with open(jsonpath, "r", encoding="utf-8") as fd: 16 | myfile = json.load(fd) 17 | return myfile 18 | 19 | 20 | def check_for_json(idstr: str, jsonpath: dict): 21 | "Check whether given id string is contained in given json object" 22 | return idstr in read_json(jsonpath) 23 | 24 | 25 | def build_project_structure(project_path: str): 26 | "given project path build project structure for easy acces to locations" 27 | structure = {} 28 | assetdir = os.path.join(project_path, "assets") 29 | predicate_dir = os.path.join(assetdir, "predicate") 30 | link_dir = os.path.join(assetdir, "link") 31 | entity_dir = os.path.join(assetdir, "entity") 32 | author_dir = os.path.join(predicate_dir, "authority") 33 | simple_dir = os.path.join(author_dir, "simple") 34 | structure["link"] = link_dir 35 | structure["entity"] = entity_dir 36 | structure["predicate"] = predicate_dir 37 | structure["author"] = author_dir 38 | structure["simple"] = simple_dir 39 | return structure 40 | 41 | 42 | def check_id_in_project(project_path: str, idstr: str) -> bool: 43 | "check id string in project" 44 | structure = build_project_structure(project_path) 45 | for asset_type, asset_path in structure.items(): 46 | jsonglob = os.path.join(asset_path, "*.json") 47 | jsonfiles = glob.glob(jsonglob) 48 | for jsonpath in jsonfiles: 49 | if check_for_json(idstr, jsonpath): 50 | return True, asset_type, jsonpath 51 | return [False] 52 | 53 | 54 | if __name__ == "__main__": 55 | project_path = input("Enter project path: ") 56 | idstr = input("Enter id string: ") 57 | check = check_id_in_project(project_path, idstr) 58 | if check[0] is False: 59 | print("id string:", 60 | idstr, "is usable.") 61 | sys.exit(0) 62 | # 63 | print("id string:", idstr, 64 | "has a duplicate in the asset type", check[1], 65 | "at", check[2]) 66 | print("Duplicate ids are not allowed, please enter another id string") 67 | sys.exit(0) 68 | -------------------------------------------------------------------------------- /tests/test_container.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: test scripts of suite 4 | 5 | import unittest 6 | import os 7 | import pdb 8 | 9 | from suite.dtype.container import Pair, Array 10 | from suite.dtype.container import ContainerMaker 11 | 12 | from suite.dtype.primitive import ConstraintString 13 | from suite.dtype.primitive import NonNumericString 14 | from suite.dtype.primitive import ConstantString 15 | from suite.dtype.primitive import PrimitiveMaker 16 | 17 | 18 | class TestContainer(unittest.TestCase): 19 | "test container.py" 20 | 21 | def setUp(self): 22 | self.currentdir = os.path.abspath(os.curdir) 23 | self.testdir = os.path.join(self.currentdir, "tests") 24 | self.assetdir = os.path.join(self.testdir, "assets") 25 | self.consMaker = PrimitiveMaker("constant string") 26 | self.constrMaker = PrimitiveMaker("constraint string") 27 | self.nnmaker = PrimitiveMaker("non numeric string") 28 | 29 | def test_pair(self): 30 | pmaker = ContainerMaker("pair") 31 | mystr1 = self.consMaker.make(mystr="mystr1") 32 | mystr2 = self.nnmaker.from_string(mystr="mystr2") 33 | mystr3 = self.consMaker.make(mystr="mystr3") 34 | check = True 35 | try: 36 | pair = pmaker.make(arg1=mystr1, arg2=mystr2) 37 | except ValueError or TypeError: 38 | check = False 39 | self.assertTrue(check, "either value or type error triggered") 40 | check = False 41 | try: 42 | pair = pmaker.make(arg1=mystr1, arg2=mystr3) 43 | except ValueError or TypeError: 44 | check = True 45 | self.assertTrue(check, "either value or type error should have been triggered") 46 | 47 | def test_array(self): 48 | pmaker = ContainerMaker("array") 49 | mystr1 = self.consMaker.make(mystr="mystr1") 50 | mystr2 = self.nnmaker.from_string(mystr="mystr2") 51 | mystr3 = self.consMaker.make(mystr="mystr3") 52 | check = True 53 | try: 54 | arr = pmaker.make(elements=[mystr1, mystr3]) 55 | except ValueError or TypeError: 56 | check = False 57 | self.assertTrue(check, "either value or type error triggered") 58 | check = False 59 | try: 60 | arr = pmaker.make(elements=[mystr1, mystr2]) 61 | except ValueError or TypeError: 62 | check = True 63 | self.assertTrue(check, "either value or type error should have been triggered") 64 | 65 | 66 | 67 | 68 | if __name__ == "__main__": 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /tests/test_structure.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: test scripts of suite 4 | 5 | import unittest 6 | import os 7 | import pdb 8 | 9 | from suite.dtype.primitive import NonNumericString 10 | from suite.dtype.container import SingleConstraintTuple 11 | from suite.dtype.container import NonNumericTuple 12 | from suite.dtype.container import UniformNonNumericMixedPair 13 | 14 | from suite.structure import SimpleStructure 15 | from suite.structure import CombinedStructure 16 | from suite.structure import LinkStructure 17 | 18 | 19 | class TestModel(unittest.TestCase): 20 | "test container.py" 21 | 22 | def setUp(self): 23 | self.currentdir = os.path.abspath(os.curdir) 24 | self.testdir = os.path.join(self.currentdir, "tests") 25 | self.assetdir = os.path.join(self.testdir, "assets") 26 | 27 | def test_simple_structure(self): 28 | "simple structure" 29 | idstr = NonNumericString("My String 2") 30 | value = "𐎠𐎭𐎠 𐏐 𐏃𐎹" 31 | definition = "adā : hya" 32 | ss = SimpleStructure(idstr=idstr, 33 | value=value, 34 | definition=definition) 35 | self.assertEqual(True, ss.isValid()) 36 | 37 | def test_combined_structure(self): 38 | "combined structure" 39 | idstr = NonNumericString("my-string-2") 40 | value = "𐎠𐎭𐎠 𐏐 𐏃𐎹" 41 | definition = "adā : hya" 42 | id2 = NonNumericString("relation-2") 43 | mystr1 = NonNumericString("word-1") 44 | mystr2 = NonNumericString("word-2") 45 | mystr3 = NonNumericString("word-3") 46 | mset = frozenset([mystr1, mystr2, mystr3]) 47 | tpl = NonNumericTuple(mset) 48 | cstruct = CombinedStructure(id1=idstr, 49 | value=value, 50 | definition=definition, 51 | id2=id2, 52 | values=tpl) 53 | self.assertEqual(True, cstruct.isValid()) 54 | 55 | def test_link_structure(self): 56 | "" 57 | idstr = NonNumericString("my-string-2") 58 | id2a = NonNumericString("relation-2") 59 | mystr1 = NonNumericString("word-1") 60 | mystr2 = NonNumericString("word-2") 61 | mystr3 = NonNumericString("word-3") 62 | mset = frozenset([mystr1, mystr2, mystr3]) 63 | tpl1 = NonNumericTuple(mset) 64 | p1 = UniformNonNumericMixedPair(id2a, 65 | tpl1) 66 | id2b = NonNumericString("relation-3") 67 | mystr4 = NonNumericString("word-4") 68 | mystr5 = NonNumericString("word-5") 69 | mystr6 = NonNumericString("word-6") 70 | mset = frozenset([mystr1, mystr2, mystr3]) 71 | tpl2 = NonNumericTuple(mset) 72 | p2 = UniformNonNumericMixedPair(id2b, 73 | tpl2) 74 | link1 = LinkStructure(idstr, 75 | frozenset([p1, p2])) 76 | self.assertEqual(True, link1.isValid()) 77 | id2c = NonNumericString("relation-3") 78 | mystr6 = NonNumericString("word-7") 79 | mset = frozenset([mystr1, mystr2, mystr6]) 80 | tpl2 = NonNumericTuple(mset) 81 | p3 = UniformNonNumericMixedPair(id2c, 82 | tpl2) 83 | link2 = LinkStructure(idstr, 84 | frozenset([p1, p2, p3])) 85 | self.assertEqual(False, link2.isValid()) 86 | 87 | 88 | if __name__ == "__main__": 89 | unittest.main() 90 | -------------------------------------------------------------------------------- /suite/dtype/container.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains containers of the suite 3 | """ 4 | # author: Kaan Eraslan 5 | # license: see, LICENSE 6 | 7 | from suite.dtype.primitive import ConstraintString, NonNumericString 8 | from suite.dtype.primitive import ConstantString 9 | from typing import NamedTuple 10 | from collections import namedtuple 11 | from types import FunctionType 12 | 13 | 14 | class BasePair: 15 | "Abstract class for all pairs in spec container" 16 | 17 | def __iter__(self): 18 | "Make a pair iterable" 19 | return iter(set((self.arg1, self.arg2))) 20 | 21 | def isValid(self): 22 | cond1 = self.arg1.isValid() 23 | cond2 = self.arg2.isValid() 24 | cond3 = type(self.arg2) != type(self.arg1) 25 | return cond1 and cond2 and cond3 26 | 27 | def __repr__(self): 28 | return "Pair: ({0}, {1})".format(repr(self.arg1), repr(self.arg2)) 29 | 30 | def __str__(self): 31 | return "Pair: ({0}, {1})".format(str(self.arg1), str(self.arg2)) 32 | 33 | def __ne__(self, other): 34 | res = self.__eq__(other) 35 | if res is not NotImplemented: 36 | return not res 37 | return NotImplemented 38 | 39 | def __hash__(self): 40 | items = list(self.__dict__.items()) 41 | items.sort() 42 | return hash(tuple(items)) 43 | 44 | def __eq_proc__(self, other): 45 | cond1 = other.arg1 == self.arg1 46 | cond2 = other.arg2 == self.arg2 47 | return cond1 and cond2 48 | 49 | 50 | class Pair(BasePair, namedtuple("Pair", "arg1 arg2")): 51 | def __eq__(self, other): 52 | if isinstance(other, Pair): 53 | return self.__eq_proc__(other) 54 | return NotImplemented 55 | 56 | def __hash__(self): 57 | items = list(self.__dict__.items()) 58 | items.sort() 59 | return hash(tuple(items)) 60 | 61 | 62 | class Array: 63 | "Models array container from spec" 64 | 65 | def __init__(self, iterable): 66 | "" 67 | check = True 68 | try: 69 | iterator = iter(iterable) 70 | except TypeError: 71 | check = False 72 | # 73 | if check is False: 74 | raise TypeError( 75 | "Provided object should be an iterable. It is of type: " 76 | + str(type(iterable)) 77 | ) 78 | firstElement = None 79 | eltypes = set() 80 | for item in iterable: 81 | eltypes.add(type(item)) 82 | 83 | if len(eltypes) > 1: 84 | mess = "Iterable contains different types: " + str(eltypes) 85 | 86 | raise ValueError(mess) 87 | validCheck = all([el.isValid() for el in iterable]) 88 | if validCheck is False: 89 | raise ValueError( 90 | "Array contains invalid objects: " 91 | + " ".join([el for el in iterable if el.isValid() is False]) 92 | ) 93 | self.elements = frozenset(iterable) 94 | 95 | def isValid(self): 96 | "If the array is initialized that it should have been valid" 97 | return True 98 | 99 | def __str__(self): 100 | return "Array: " + " ".join([el for el in self.elements]) 101 | 102 | def __eq__(self, other): 103 | if isinstance(other, Array): 104 | return self.elements == other.elements 105 | return NotImplemented 106 | 107 | def __ne__(self, other): 108 | res = self.__eq__(other) 109 | if res is not NotImplemented: 110 | return not res 111 | return NotImplemented 112 | 113 | def __hash__(self): 114 | items = list(self.__dict__.items()) 115 | items.sort() 116 | return hash(tuple(items)) 117 | 118 | 119 | class ContainerMaker: 120 | "Container maker" 121 | 122 | def __init__(self, choice: str): 123 | self.choice = choice 124 | 125 | def make_pair(self, arg1, arg2): 126 | "Make pair using arg1 and arg2" 127 | p = Pair(arg1=arg1, arg2=arg2) 128 | if p.isValid() is False: 129 | mess = "\n - each argument is valid" 130 | mess += mess + "\n - arguments are of different type" 131 | raise ValueError( 132 | "Pair initialized with invalid parameters. Make sure: " + mess 133 | ) 134 | return 135 | 136 | def make_array(self, els): 137 | "make array using iterable" 138 | arr = Array(iterable=els) 139 | if arr.isValid() is False: 140 | mess = "\n - each argument is valid" 141 | mess += mess + "\n - arguments are of same type" 142 | raise ValueError( 143 | "Array initialized with invalid parameters. Make sure: " + mess 144 | ) 145 | return arr 146 | 147 | @classmethod 148 | def from_type(cls, objType, **kwargs): 149 | objname = objType.__name__ 150 | if objname == "Pair": 151 | arg1, arg2 = kwargs["arg1"], kwargs["arg2"] 152 | return cls.make_pair(arg1=arg1, arg2=arg2) 153 | elif objname == "Array": 154 | els = kwargs["elements"] 155 | return cls.make_array(els) 156 | else: 157 | raise ValueError("Unknown object type: " + objname) 158 | 159 | def make(self, **kwargs): 160 | "make object based on choice" 161 | choice = self.choice.lower() 162 | if choice == "pair": 163 | arg1, arg2 = kwargs["arg1"], kwargs["arg2"] 164 | return self.make_pair(arg1, arg2) 165 | elif choice == "array": 166 | els = kwargs["elements"] 167 | return self.make_array(els) 168 | else: 169 | raise ValueError("Unknown choice: " + choice) 170 | -------------------------------------------------------------------------------- /tests/test_primitive.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: test scripts of suite 4 | 5 | import unittest 6 | import os 7 | import pdb 8 | 9 | from suite.dtype.primitive import PrimitiveMaker 10 | from suite.dtype.primitive import ConstantString 11 | from suite.dtype.primitive import NonNumericString 12 | from suite.dtype.primitive import ConstraintString 13 | 14 | 15 | class TestPrimitive(unittest.TestCase): 16 | def setUp(self): 17 | self.currentdir = os.path.abspath(os.curdir) 18 | self.testdir = os.path.join(self.currentdir, "tests") 19 | self.assetdir = os.path.join(self.testdir, "assets") 20 | 21 | def test_primitive_maker_choice(self): 22 | check = False 23 | try: 24 | pmaker = PrimitiveMaker("weird choice") 25 | pmaker.make(mystr="my string") 26 | except ValueError: 27 | check = True 28 | self.assertEqual(check, True) 29 | 30 | def test_primitive_maker_from_type(self): 31 | "test making object from type" 32 | pmaker = PrimitiveMaker 33 | check = False 34 | try: 35 | pmaker.from_type(int, mystr=252) 36 | except ValueError: 37 | check = True 38 | self.assertTrue(check) 39 | check = True 40 | try: 41 | pmaker.from_type(ConstantString, mystr="here is a string") 42 | except TypeError or ValueError: 43 | check = False 44 | self.assertTrue(check) 45 | check = False 46 | try: 47 | pmaker.from_type(ConstraintString, mystr="here is a string") 48 | except TypeError: 49 | check = True 50 | self.assertTrue(check) 51 | check = True 52 | try: 53 | mystr = pmaker.from_type(ConstantString, mystr="a str") 54 | 55 | def lfn(x): 56 | return x.constr.islower() 57 | 58 | pmaker.from_type(ConstraintString, mystr=mystr, fnc=lfn) 59 | except TypeError: 60 | check = True 61 | self.assertTrue(check) 62 | 63 | def test_primitive_maker_from_str(self): 64 | pmaker = PrimitiveMaker("constant string") 65 | cmaker = PrimitiveMaker("constraint string") 66 | check = True 67 | try: 68 | constr = pmaker.from_string(mystr="a str") 69 | except ValueError or TypeError: 70 | check = False 71 | self.assertTrue(check) 72 | 73 | def lfn(x): 74 | return x.constr.islower() 75 | 76 | check = True 77 | try: 78 | constr = cmaker.from_string(mystr="another str", fnc=lfn) 79 | except ValueError or TypeError: 80 | check = False 81 | self.assertTrue(check) 82 | 83 | def test_primitive_maker_constant_string(self): 84 | pmaker = PrimitiveMaker("char") 85 | check = False 86 | try: 87 | pmaker.make_constant_string(1235) 88 | except TypeError: 89 | check = True 90 | self.assertTrue(check) 91 | check = False 92 | try: 93 | pmaker.make_constant_string(1235.05) 94 | except TypeError: 95 | check = True 96 | self.assertTrue(check) 97 | check = False 98 | try: 99 | pmaker.make_constant_string(True) 100 | except TypeError: 101 | check = True 102 | self.assertTrue(check) 103 | 104 | def test_constant_string(self): 105 | "test for constant string primitive" 106 | pmaker = PrimitiveMaker("constant string") 107 | myconstr1 = pmaker.make(mystr="my true constant string and unicode š") 108 | myconstr2 = pmaker.make(mystr="my true constant string and unicode š") 109 | myconstr3 = pmaker.make(mystr="my true constant string and unicode ḫ") 110 | self.assertEqual(True, myconstr1.isValid()) 111 | self.assertEqual(False, myconstr1 == myconstr3) 112 | self.assertEqual(True, myconstr1 == myconstr2) 113 | 114 | def test_constraint_string(self): 115 | pmaker = PrimitiveMaker("constant string") 116 | 117 | def lfn(x: ConstantString): 118 | return x.constr.islower() 119 | 120 | costr1 = pmaker.make(mystr="my true string") 121 | costr2 = pmaker.make(mystr="MY False String") 122 | cstr1 = pmaker.from_type(primitiveType=ConstraintString, mystr=costr1, fnc=lfn) 123 | check = False 124 | try: 125 | cstr2 = pmaker.from_type( 126 | primitiveType=ConstraintString, mystr=costr2, fnc=lfn 127 | ) 128 | except ValueError: 129 | check = True 130 | 131 | self.assertEqual(True, cstr1.isValid()) 132 | self.assertEqual(True, check) 133 | 134 | def test_nonnumeric_string(self): 135 | pmaker = PrimitiveMaker("constant string") 136 | costr1 = pmaker.make(mystr="my true string") 137 | costr2 = pmaker.make(mystr="1 my another true string 1") 138 | costr3 = pmaker.make(mystr="123") 139 | costr4 = pmaker.make(mystr="123.45") 140 | costr5 = pmaker.make(mystr="123j") 141 | costr6 = pmaker.make(mystr="123/12335") 142 | 143 | pmaker = PrimitiveMaker("non numeric string") 144 | cstr1 = pmaker.make(mystr=costr1) 145 | cstr2 = pmaker.make(mystr=costr2) 146 | check = False 147 | try: 148 | cstr3 = pmaker.make(mystr=costr3) 149 | except ValueError: 150 | check = True 151 | self.assertEqual(True, check) 152 | check = False 153 | try: 154 | cstr4 = pmaker.make(mystr=costr4) 155 | except ValueError: 156 | check = True 157 | self.assertEqual(True, check) 158 | check = False 159 | try: 160 | cstr5 = pmaker.make(mystr=costr5) 161 | except ValueError: 162 | check = True 163 | self.assertEqual(True, check) 164 | check = False 165 | try: 166 | cstr6 = pmaker.make(mystr=costr6) 167 | except ValueError: 168 | check = True 169 | self.assertEqual(True, check) 170 | 171 | 172 | if __name__ == "__main__": 173 | unittest.main() 174 | -------------------------------------------------------------------------------- /suite/structure.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains structures 3 | that are used for documents used by the project 4 | """ 5 | # author: Kaan Eraslan 6 | # license: see, LICENSE 7 | 8 | from suite.dtype.primitive import NonNumericString 9 | from suite.dtype.container import Pair 10 | from suite.dtype.container import NonNumericTuple 11 | from suite.dtype.container import UniformNonNumericMixedPair 12 | 13 | from suite.dtype.container import SingleConstraintTuple 14 | 15 | 16 | class Structure: 17 | "Abstract class for all structure" 18 | 19 | def __init__(self): 20 | pass 21 | 22 | def isValidComponent(self): 23 | "" 24 | raise NotImplementedError 25 | 26 | def isValid(self): 27 | "" 28 | raise NotImplementedError 29 | 30 | def make(self): 31 | "" 32 | raise NotImplementedError 33 | 34 | def __ne__(self, other): 35 | res = self.__eq__(other) 36 | if res is not NotImplemented: 37 | return not res 38 | return NotImplemented 39 | 40 | def __hash__(self): 41 | items = list(self.__dict__.items()) 42 | items.sort() 43 | return hash(tuple(items)) 44 | 45 | 46 | class SimpleStructure(Structure): 47 | "Simple structure" 48 | 49 | def __init__(self, idstr: NonNumericString, value: str, definition: str): 50 | assert isinstance(idstr, NonNumericString) 51 | assert isinstance(value, str) 52 | assert isinstance(definition, str) 53 | self.idstr = idstr 54 | self.value = value 55 | self.definition = definition 56 | 57 | def isValidComponent(self): 58 | return self.idstr.isValid() 59 | 60 | def isValid(self): 61 | ntpl = self.make() 62 | return self.isValidComponent() and isinstance(ntpl, frozenset) 63 | 64 | def make(self): 65 | "make structure" 66 | pair = Pair(str1=self.value, str2=self.definition) 67 | return frozenset((self.idstr, pair)) 68 | 69 | def __str__(self): 70 | return "Simple Structure: " + ",".join([str(e) for e in self.make()]) 71 | 72 | def __eq__(self, other): 73 | if isinstance(other, SimpleStructure): 74 | cond1 = other.idstr == self.idstr 75 | cond2 = other.value == self.value 76 | cond3 = other.definition == self.definition 77 | return cond1 and cond2 and cond3 78 | return NotImplemented 79 | 80 | def __hash__(self): 81 | items = list(self.__dict__.items()) 82 | items.sort() 83 | return hash(tuple(items)) 84 | 85 | 86 | class CombinedStructure(Structure): 87 | "Models spec structure Combined" 88 | 89 | def __init__(self, id1: NonNumericString, 90 | value: str, definition: str, 91 | id2: NonNumericString, 92 | values: NonNumericTuple): 93 | assert isinstance(id1, NonNumericString) 94 | self.id1 = id1 95 | assert isinstance(value, str) 96 | self.value = value 97 | assert isinstance(definition, str) 98 | self.definition = definition 99 | assert isinstance(id2, NonNumericString) 100 | self.id2 = id2 101 | assert isinstance(values, NonNumericTuple) 102 | assert all([isinstance(val, NonNumericString) for val in values]) 103 | self.values = values 104 | 105 | def isValidComponent(self): 106 | cond1 = self.id1.isValid() 107 | cond2 = self.id2.isValid() 108 | cond3 = self.values.isValid() 109 | return cond1 and cond2 and cond3 110 | 111 | def make(self): 112 | "" 113 | pair = Pair(str1=self.value, str2=self.definition) 114 | npair = UniformNonNumericMixedPair(str1=self.id2, strset=self.values) 115 | return frozenset((self.id1, pair, npair)) 116 | 117 | def isValid(self): 118 | "" 119 | cond1 = self.isValidComponent() 120 | tpl = self.make() 121 | cond2 = all([t.isValid() for t in tpl]) 122 | return cond1 and cond2 and isinstance(tpl, frozenset) 123 | 124 | def __str__(self): 125 | return "Combined Structure: " + ",".join([str(e) for e in self.make()]) 126 | 127 | def __eq__(self, other): 128 | if isinstance(other, CombinedStructure): 129 | cond1 = other.id1 == self.id1 130 | cond2 = other.value == self.value 131 | cond3 = other.definition == self.definition 132 | cond4 = other.values == self.values 133 | cond5 = other.id2 == self.id2 134 | return bool(cond1 and 135 | cond2 and 136 | cond3 and 137 | cond4 and 138 | cond5) 139 | return NotImplemented 140 | 141 | def __hash__(self): 142 | items = list(self.__dict__.items()) 143 | items.sort() 144 | return hash(tuple(items)) 145 | 146 | 147 | class LinkStructure(Structure): 148 | "Models spec structure Link" 149 | 150 | def __init__(self, id1: NonNumericString, 151 | id2_ids: frozenset): 152 | assert isinstance(id1, NonNumericString) 153 | assert isinstance(id2_ids, frozenset) 154 | assert all( 155 | [isinstance(p, UniformNonNumericMixedPair) for p in id2_ids] 156 | ) 157 | self.id1 = id1 158 | self.id2_ids = id2_ids 159 | 160 | def isValidComponent(self): 161 | cond1 = all([p.isValid() for p in self.id2_ids]) 162 | return self.id1.isValid() and cond1 163 | 164 | def hasUniqueId2s(self): 165 | "check if link structure have unique id2" 166 | id2s = set() 167 | for pair in self.id2_ids: 168 | id2 = pair.arg1 169 | if id2 not in id2s: 170 | id2s.add(id2) 171 | else: 172 | return False 173 | return True 174 | 175 | def make(self): 176 | "make an object" 177 | return frozenset((self.id1, self.id2_ids)) 178 | 179 | def isValid(self): 180 | cond1 = self.isValidComponent() 181 | tpl = self.make() 182 | cond2 = isinstance(tpl, frozenset) 183 | cond3 = self.hasUniqueId2s() 184 | return cond1 and cond2 and cond3 185 | 186 | def __str__(self): 187 | return "Link Structure: " + ",".join([str(e) for e in self.make()]) 188 | 189 | def __eq__(self, other): 190 | if isinstance(other, LinkStructure): 191 | cond1 = other.id1 == self.id1 192 | cond2 = other.id2_ids == self.id2_ids 193 | return cond1 and cond2 194 | return NotImplemented 195 | 196 | def __hash__(self): 197 | items = list(self.__dict__.items()) 198 | items.sort() 199 | return hash(tuple(items)) 200 | -------------------------------------------------------------------------------- /suite/projectMaker.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: set up a project directory for digital publishing and conservation 4 | # project 5 | 6 | import os 7 | import json 8 | import argparse 9 | 10 | 11 | def assert_first_not_second_proc(path1: str, path2: str) -> None: 12 | "assert first path as true and second not true with isdir" 13 | assert os.path.isdir(path1) 14 | assert not os.path.isdir(path2) 15 | 16 | 17 | def write_to_json(path: str, obj: dict) -> None: 18 | "write object to path as json" 19 | with open(path, "w", encoding="utf-8") as fd: 20 | json.dump(obj, fd, ensure_ascii=False, indent=2) 21 | return 22 | 23 | 24 | def mk_project_dir(mainpath: str, project_name: str) -> str: 25 | "make project directory in given main path" 26 | project_path = os.path.join(mainpath, project_name) 27 | assert_first_not_second_proc(mainpath, project_path) 28 | os.mkdir(project_path) 29 | return project_path 30 | 31 | 32 | def mk_project_assets(project_path: str) -> str: 33 | "make assets directory in project" 34 | asset_path = os.path.join(project_path, "assets") 35 | assert_first_not_second_proc(project_path, asset_path) 36 | os.mkdir(asset_path) 37 | return asset_path 38 | 39 | 40 | def mk_project_predicate(asset_path: str) -> str: 41 | "make predicate directory in project assets" 42 | predicate_path = os.path.join(asset_path, "predicate") 43 | assert_first_not_second_proc(asset_path, predicate_path) 44 | os.mkdir(predicate_path) 45 | return predicate_path 46 | 47 | 48 | def mk_project_link(asset_path: str) -> str: 49 | "make predicate directory in project assets" 50 | link_path = os.path.join(asset_path, "link") 51 | assert_first_not_second_proc(asset_path, link_path) 52 | os.mkdir(link_path) 53 | return link_path 54 | 55 | 56 | def mk_project_entity(asset_path: str) -> str: 57 | "make entity directory in project assets" 58 | entity_path = os.path.join(asset_path, "entity") 59 | assert_first_not_second_proc(asset_path, entity_path) 60 | os.mkdir(entity_path) 61 | return entity_path 62 | 63 | 64 | def mk_project_authority(predicate_path: str) -> str: 65 | "make authority directory in project assets" 66 | author_dir = os.path.join(predicate_path, "authority") 67 | assert_first_not_second_proc(predicate_path, author_dir) 68 | os.mkdir(author_dir) 69 | return author_dir 70 | 71 | 72 | def mk_simple_authority(author_dir: str) -> str: 73 | "make simple authority directory in project authority directory" 74 | simple_dir = os.path.join(author_dir, "simple") 75 | assert_first_not_second_proc(author_dir, simple_dir) 76 | os.mkdir(simple_dir) 77 | return simple_dir 78 | 79 | 80 | def mk_project_dirs(mainpath: str, project_name: str): 81 | "make project directories" 82 | project_dir = mk_project_dir(mainpath, project_name) 83 | asset_dir = mk_project_assets(project_dir) 84 | predicate_dir = mk_project_predicate(asset_dir) 85 | link_dir = mk_project_link(asset_dir) 86 | entity_dir = mk_project_entity(asset_dir) 87 | author_dir = mk_project_authority(predicate_dir) 88 | simple_dir = mk_simple_authority(author_dir) 89 | return simple_dir, author_dir, entity_dir, link_dir, predicate_dir 90 | 91 | 92 | def mk_sample_document(parent_path: str, 93 | doc_name: str, sample_doc: dict) -> None: 94 | "make sample document given parent path and its name with its structure" 95 | assert os.path.isdir(parent_path) 96 | doc_path = os.path.join(parent_path, doc_name) 97 | write_to_json(doc_path, sample_doc) 98 | 99 | 100 | def mk_sample_simple_authority(simple_dir: str) -> None: 101 | "make a sample simple authority document" 102 | sample_doc = { 103 | "sample-word-1": {"lorem": ""}, 104 | "sample-word-2": {"ipsum": ""}, 105 | "sample-word-3": {"dolor": ""}, 106 | "sample-word-4": {"sit": ""}, 107 | "sample-word-5": {"amet": ""}, 108 | "sample-simple-n": {"value": "value definition"} 109 | } 110 | mk_sample_document(simple_dir, "sampleSimple.json", sample_doc) 111 | 112 | 113 | def mk_sample_combined_authority(author_dir: str) -> None: 114 | "make a sample combined authority document" 115 | sample_doc = { 116 | "sample-combined-grammar-1": { 117 | "coordinating conjunction": "", 118 | "sample-relation-2": {"0": "sample-grammar-1"} 119 | }, 120 | "sample-combined-grammar-2": { 121 | "feminine substantif": "", 122 | "sample-relation-2": { 123 | "0": "sample-grammar-6", "1": "sample-grammar-2" 124 | } 125 | }, 126 | "sample-combined-n": { 127 | "value": "value definition", 128 | "sample-relation-n": { 129 | "0": "sample-simple-id", 130 | "1": "sample-simple-id-n" 131 | } 132 | } 133 | } 134 | mk_sample_document(author_dir, "sampleCombinedSimple.json", sample_doc) 135 | 136 | 137 | def mk_sample_predicate(asset_path: str) -> None: 138 | "make sample predicate document" 139 | sample_doc = { 140 | "sample-predicate-1": { 141 | "sample-relation-3": { 142 | "0": "sample-combined-grammar-2", 143 | "1": "sample-grammar-5", 144 | "2": "sample-grammar-7", 145 | }, 146 | "sample-relation-1": {"0": "sample-word-3"} 147 | } 148 | } 149 | mk_sample_document(asset_path, "samplePredicate.json", sample_doc) 150 | 151 | 152 | def mk_sample_entity_relation(asset_path: str) -> None: 153 | "make sample entity relations document" 154 | sample_doc = { 155 | "sample-entity-1": { 156 | "sample-relation-1": {"0": "sample-word-2"} 157 | }, 158 | "sample-entity-2": { 159 | "sample-relation-2": {"0": "sample-entity-1"} 160 | } 161 | } 162 | mk_sample_document(asset_path, "sampleEntityRelations.json", sample_doc) 163 | 164 | 165 | def mk_sample_entity_predicate_link(asset_path: str) -> None: 166 | "make sample entity predicate link document" 167 | sample_doc = { 168 | "sample-entity-1": { 169 | "sample-relation-3": {"0": "sample-predicate-1"} 170 | }, 171 | "sample-entity-2": { 172 | "sample-relation-3": {"0": "sample-predicate-2"} 173 | } 174 | } 175 | mk_sample_document( 176 | asset_path, "sampleEntityPredicateLink.json", sample_doc) 177 | 178 | 179 | def make_samples_proc(simple_dir, author_dir, 180 | predicate_dir, entity_dir, link_dir): 181 | "make samples procedure" 182 | mk_sample_simple_authority(simple_dir) 183 | mk_sample_combined_authority(author_dir) 184 | mk_sample_predicate(predicate_dir) 185 | mk_sample_entity_relation(entity_dir) 186 | mk_sample_entity_predicate_link(link_dir) 187 | 188 | 189 | if __name__ == "__main__": 190 | main_path = input("enter parent directory for project: ") 191 | project_name = input("Enter a project name: ") 192 | main_dir = os.path.abspath(main_path) 193 | (simple_dir, author_dir, 194 | entity_dir, link_dir, predicate_dir) = mk_project_dirs(main_dir, 195 | project_name) 196 | make_samples_proc(simple_dir, author_dir, predicate_dir, entity_dir, 197 | link_dir) 198 | print("project structure done") 199 | -------------------------------------------------------------------------------- /tests/test_io_primitive.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: test scripts of suite 4 | 5 | import unittest 6 | import os 7 | import pdb 8 | 9 | from lxml import etree 10 | import json 11 | import dill 12 | 13 | from suite.dtype.primitive import ConstraintString, NonNumericString 14 | from suite.dtype.primitive import ConstantString 15 | from suite.io.iprimitive import ConstraintStringIo 16 | from suite.io.iprimitive import NonNumericStringIo 17 | from suite.io.iprimitive import ConstantStringIo 18 | 19 | 20 | class TestIoIPrimitive(unittest.TestCase): 21 | "test io primitive module" 22 | 23 | def setUp(self): 24 | "setup small data" 25 | 26 | def lfn(x: ConstantString): 27 | return x.constr.islower() 28 | 29 | mcons = ConstantString("my valid constraint string") 30 | self.mycstr = ConstraintString(mcons, lfn) 31 | self.myfn = dill.dumps(self.mycstr.fn) 32 | mncons = ConstantString("my valid non numeric string") 33 | self.mynnstr = NonNumericString(mncons) 34 | self.mynnfn = dill.dumps(self.mynnstr.fn) 35 | self.myconstr = ConstantString("my valid constant string") 36 | self.myconstio = ConstantStringIo(self.myconstr) 37 | 38 | def test_constant_str_to_xml(self): 39 | cstrio = ConstantStringIo(self.myconstr) 40 | ioinst = cstrio.getIoInstance("xml") 41 | default = ioinst.to_element() 42 | cmpel = etree.Element("primitive") 43 | cmpel.set("class", "ConstantString") 44 | cmpel.text = str(self.myconstr) 45 | self.assertEqual(default.text, cmpel.text) 46 | self.assertEqual(default.attrib, cmpel.attrib) 47 | 48 | def test_constant_str_to_json(self): 49 | cstrio = ConstantStringIo(self.myconstr) 50 | ioinst = cstrio.getIoInstance("json") 51 | defjson = ioinst.to_json() 52 | defdict = ioinst.to_dict() 53 | cmpd = {} 54 | cmpd["class"] = "ConstantString" 55 | cmpd["type"] = "primitive" 56 | cmpd["value"] = str(self.myconstr) 57 | cmpstr = json.dumps(cmpd, ensure_ascii=False, indent=2, sort_keys=True) 58 | self.assertEqual(cmpd, defdict) 59 | self.assertEqual(cmpstr, defjson) 60 | 61 | def test_constant_str_from_json(self): 62 | consio = ConstantStringIo 63 | jio = ConstantStringIo.getIoClass("json") 64 | cmpd = {} 65 | cmpd["class"] = "ConstantString" 66 | cmpd["type"] = "primitive" 67 | cmpd["value"] = str(self.myconstr) 68 | cmpstr = json.dumps(cmpd, ensure_ascii=False, indent=2, sort_keys=True) 69 | myconst = jio.from_json(cmpstr) 70 | self.assertEqual(myconst, self.myconstr) 71 | 72 | def test_constant_str_from_xml(self): 73 | xmlio = ConstantStringIo.getIoClass("xml") 74 | cmpel = etree.Element("primitive") 75 | cmpel.set("class", "ConstantString") 76 | cmpel.text = str(self.myconstr) 77 | cstr = xmlio.from_element(cmpel) 78 | self.assertEqual(cstr, self.myconstr) 79 | 80 | def test_constraint_str_to_xml(self): 81 | cstrio = ConstraintStringIo(self.mycstr) 82 | ioinst = cstrio.getIoInstance("xml") 83 | default = ioinst.to_element() 84 | cmpel = etree.Element("primitive", constraint=self.myfn.hex()) 85 | cmpel.set("class", "ConstraintString") 86 | cmpel.text = str(self.mycstr) 87 | self.assertEqual(default.text, cmpel.text) 88 | self.assertEqual(default.attrib, cmpel.attrib) 89 | 90 | def test_constraint_str_to_json(self): 91 | cstrio = ConstraintStringIo(self.mycstr) 92 | consio = ConstantStringIo(self.mycstr.cstr) 93 | cionst = consio.getIoInstance("json") 94 | ioinst = cstrio.getIoInstance("json") 95 | default = ioinst.to_json() 96 | dicrepr = ioinst.to_dict() 97 | cmpd = {} 98 | cmpd["class"] = "ConstraintString" 99 | cmpd["type"] = "primitive" 100 | cmpd["value"] = cionst.to_dict() 101 | cmpd["constraint"] = self.myfn.hex() 102 | reprstr = json.dumps(cmpd, ensure_ascii=False, indent=2, sort_keys=True) 103 | self.assertEqual(default, reprstr) 104 | self.assertEqual(dicrepr, cmpd) 105 | 106 | def test_constraint_str_from_json(self): 107 | "" 108 | jio = ConstraintStringIo.getIoClass("json") 109 | consio = ConstantStringIo(self.mycstr.cstr) 110 | ioinst = consio.getIoInstance("json") 111 | consjson = ioinst.to_json() 112 | cmpd = {} 113 | cmpd["class"] = "ConstraintString" 114 | cmpd["type"] = "primitive" 115 | cmpd["value"] = json.loads(consjson) 116 | cmpd["constraint"] = self.myfn.hex() 117 | reprstr = json.dumps(cmpd, ensure_ascii=False, indent=2, sort_keys=True) 118 | cstr = jio.from_json(reprstr) 119 | self.assertEqual(self.mycstr, cstr) 120 | 121 | def test_constraint_str_from_xml(self): 122 | xmlio = ConstraintStringIo.getIoClass("xml") 123 | cmpel = etree.Element("primitive", constraint=self.myfn.hex()) 124 | cmpel.set("class", "ConstraintString") 125 | cmpel.text = str(self.mycstr) 126 | cstr = xmlio.from_element(cmpel) 127 | self.assertEqual(cstr, self.mycstr) 128 | 129 | def test_non_numeric_string_to_xml(self): 130 | nnstrio = NonNumericStringIo(self.mynnstr) 131 | ioinst = nnstrio.getIoInstance("xml") 132 | default = ioinst.to_element() 133 | cmpel = etree.Element("primitive", constraint=self.mynnfn.hex()) 134 | cmpel.set("class", "NonNumericString") 135 | cmpel.text = str(self.mynnstr) 136 | self.assertEqual(default.text, cmpel.text) 137 | self.assertEqual(default.attrib, cmpel.attrib) 138 | 139 | def test_non_numeric_string_to_json(self): 140 | consio = ConstantStringIo(self.mynnstr.cstr) 141 | ioinst = consio.getIoInstance("json") 142 | consjson = ioinst.to_json() 143 | cstrio = NonNumericStringIo(self.mynnstr) 144 | ioinst = cstrio.getIoInstance("json") 145 | default = ioinst.to_json() 146 | dicrepr = ioinst.to_dict() 147 | cmpd = {} 148 | cmpd["class"] = "NonNumericString" 149 | cmpd["type"] = "primitive" 150 | cmpd["value"] = json.loads(consjson) 151 | cmpd["constraint"] = self.mynnfn.hex() 152 | reprstr = json.dumps(cmpd, ensure_ascii=False, indent=2, sort_keys=True) 153 | self.assertEqual(default, reprstr) 154 | self.assertEqual(dicrepr, cmpd) 155 | 156 | def test_non_numeric_string_from_json(self): 157 | "" 158 | consio = ConstantStringIo(self.mynnstr.cstr) 159 | ioinst = consio.getIoInstance("json") 160 | consjson = ioinst.to_json() 161 | jio = NonNumericStringIo.getIoClass("json") 162 | cmpd = {} 163 | cmpd["class"] = "NonNumericString" 164 | cmpd["type"] = "primitive" 165 | cmpd["value"] = json.loads(consjson) 166 | cmpd["constraint"] = self.mynnfn.hex() 167 | reprstr = json.dumps(cmpd, ensure_ascii=False, indent=2, sort_keys=True) 168 | nnstr = jio.from_json(reprstr) 169 | self.assertEqual(self.mynnstr, nnstr) 170 | 171 | def test_non_numeric_string_from_xml(self): 172 | xmlio = NonNumericStringIo.getIoClass("xml") 173 | cmpel = etree.Element("primitive", constraint=self.mynnfn.hex()) 174 | cmpel.set("class", "NonNumericString") 175 | cmpel.text = str(self.mynnstr) 176 | nnstr = xmlio.from_element(cmpel) 177 | self.assertEqual(nnstr, self.mynnstr) 178 | 179 | 180 | if __name__ == "__main__": 181 | unittest.main() 182 | -------------------------------------------------------------------------------- /docs/specs.rst: -------------------------------------------------------------------------------- 1 | ############### 2 | Specifications 3 | ############### 4 | 5 | Wording in this document conforms to `RFC 2119 6 | `_. 7 | 8 | Primitives 9 | =========== 10 | 11 | All primitives are immutable. 12 | 13 | - :code:`Character`: A character is defined to be that which has a unicode 14 | code point value. 15 | 16 | - :code:`FloatingPoint`: A floating number. Its representation consist of :code:`[-]NumericExpression.NumericExpression`. 17 | For example, :code:`64.4253` or :code:`-68.123328`. 18 | 19 | - :code:`String`: A string is defined to be a ordered list of characters. 20 | 21 | - :code:`Constant String`: a :code:`String` whose size, number of characters, 22 | and elements, characters, can not change once initiated. 23 | 24 | - :code:`Constraint String`: a :code:`Constant String` who satisfies a name 25 | bound constraint that evaluates to a boolean condition. 26 | 27 | - :code:`Non Numeric String`: a :code:`Constraint String` whose elements do 28 | not only consist of characters that can be evaluated as numeric expressions. 29 | 30 | 31 | Containers 32 | =========== 33 | 34 | 35 | All containers are immutable. 36 | 37 | - :code:`Array`: a set with n members whose elements are of the 38 | same type. 39 | 40 | - :code:`Pair`: a set with 2 members whose elements are of different 41 | type. 42 | 43 | 44 | Format 45 | ====== 46 | 47 | This document contains specifications for the files that is used and 48 | produced in this project. 49 | 50 | There are 3 structures used by this suite: 51 | 52 | - Simple 53 | 54 | - Combined 55 | 56 | - Link 57 | 58 | Simple 59 | ------- 60 | 61 | Components 62 | +++++++++++ 63 | 64 | Simple must have three components: :code: `id, value, definition`. 65 | 66 | - :code:`id`: a subclass of :code:`Non Numeric String` 67 | - :code:`value`: a subclass of :code:`Constant String` 68 | - :code:`definition`: a subclass of :code:`Constant String` 69 | 70 | :code:`definition` is associated to :code:`value`. 71 | The cardinality of the association is 1-1. 72 | Together their type is :code:`Pair` of size 2. 73 | 74 | The array :code:`value-definition` is associated to :code:`id`. 75 | The cardinality of the association is 1-1. 76 | Together their type is :code:`Pair`. 77 | 78 | Recommendations 79 | ++++++++++++++++ 80 | 81 | It is recommended to use string as the data type for all the 82 | components. 83 | It is also recommended to use alpha numeric caracters in 84 | :code:`id` field, with :code:`,` (:code:`U+002C`, virgule) as 85 | separator if necessary. 86 | 87 | Form 88 | +++++ 89 | 90 | Simple Structure may have the following form: 91 | 92 | .. code:: json 93 | 94 | {"id": {"value": "definition"}} 95 | 96 | 97 | Combined 98 | --------- 99 | 100 | Components 101 | ++++++++++++ 102 | 103 | Combined must have five components: 104 | :code:`id1, value, definition, id2, values`: 105 | 106 | - :code:`id1`: a subclass of :code:`Non Numeric String` 107 | - :code:`value`: a subclass of :code:`Constant String` 108 | - :code:`definition`: a subclass of :code:`Constant String` 109 | - :code:`id2`: a subclass of :code:`Non Numeric String` 110 | - :code:`values`: a subclass of :code:`Array`, whose elements are of 111 | :code:`Non Numeric String` 112 | 113 | :code:`definition` is associated to :code:`value`. 114 | The cardinality of the association is 1-1. 115 | Together their type is :code:`Pair`. 116 | 117 | 118 | :code:`id2` is associated to :code:`values`. 119 | The cardinality of the association is 1-1. 120 | Together their type is :code:`Pair`. The constraint 121 | that applies to both of the components is :code:`Non Numeric String` 122 | 123 | 124 | :code:`id2-values` is associated to :code:`value-definition`. 125 | The cardinality of the association is 1-1. 126 | Together their type is :code:`Pair` 127 | 128 | :code:`id1` is associated to :code:`id2-values-value-definition`. 129 | The cardinality of the association is 1-1. 130 | Together their type is :code:`Pair` 131 | 132 | 133 | 134 | Recommendations 135 | ++++++++++++++++ 136 | 137 | 138 | Form 139 | +++++ 140 | 141 | Combined structure may have the following form 142 | 143 | .. code:: json 144 | 145 | {"id1": {"value": "definition", "id2": ["id3", "id4", "id5"]}} 146 | 147 | 148 | Link 149 | ----- 150 | 151 | Components 152 | ++++++++++++ 153 | 154 | Link must have three components :code:`id1, id2, ids`: 155 | 156 | - :code:`id1`: a :code:`Non Numeric String` 157 | 158 | - :code:`id2-ids`: a :code:`Array` whose members are :code:`Pair` composed of: 159 | 160 | - :code:`id2`: a :code:`Non Numeric String` 161 | - :code:`ids` a :code:`Array` whose members are of :code:`Non Numeric String` 162 | 163 | :code:`id1` is associated to :code:`id2-ids`. 164 | The cardinality of the association is 1-1. 165 | Together their type is :code:`Pair` 166 | 167 | 168 | Recommendations 169 | ++++++++++++++++ 170 | 171 | 172 | Form 173 | +++++ 174 | 175 | Link structure may have the following form 176 | 177 | .. code:: json 178 | 179 | {"id1": {"id2": ["id3", "id4", "id5"], "id6": ["id7", "id8"]}} 180 | 181 | 182 | Content 183 | ======== 184 | 185 | There are 5 content types used by this suite: 186 | 187 | - Singular: has Simple structure 188 | 189 | - Unit: has Combined structure 190 | 191 | - Relation: has Combined structure 192 | 193 | - Predicate: has Link structure 194 | 195 | - Entity: has Link structure 196 | 197 | Underlaying Mathematical Object 198 | -------------------------------- 199 | 200 | The underlying mathematical object for our model is 201 | a directed hyper graph where nodes are singular. 202 | Unit is a hyperedge, just like a relation. Predicate or 203 | Entity is a grouping of units with different relations. 204 | 205 | Relation models a differentiable function, so suite has to ensure 206 | that they stay continous and differentiable. 207 | 208 | Recommendations 209 | =============== 210 | 211 | One should standardise the set of relations between a set of predicates and an 212 | entity. Thus at least one simple authority document should be reserved for 213 | relations between a set of predicates and an entity. These relations can be 214 | used outside of their context, but not the inverse, that is a set of 215 | predicates and an entity can not use other relations besides these. This 216 | standardisation procedure is recommended for other documents that use 217 | relations as well. It is necessary to decide this early on since it governs 218 | the mathematical model underlaying the project. 219 | 220 | 221 | One should also distinguish another representation of a phenomenon from its 222 | definition, a definition can be applied to multiple representations of a 223 | phenomenon, and a representation is that which one can apply the definition of 224 | a phenomenon. A suggestion might be to use "defined as" relation for terms of 225 | definitions and "equals" for representations. 226 | 227 | Qualifiers for representations of phenomena can be implemented using relations 228 | as well. It is recommended to use combined authority documents for modeling 229 | these qualifiers. 230 | 231 | Another suggestion is to use active verbs when defining relations since they 232 | should lend themselves easily to a usage of functions. They are treated in 233 | effect as a function where the domain is the parent item containing it and 234 | co-domain is the array of items that it maps to, so active verbs help with 235 | their modeling. 236 | 237 | Relations must be differentiable, that is for each parent item, the relation 238 | must map to only a unique set of items. When given a parent item, and 239 | a relation, there must be only one output that results from an evaluation of 240 | relation on parent item. 241 | -------------------------------------------------------------------------------- /tests/test_io_container.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: test scripts of suite 4 | 5 | import unittest 6 | import os 7 | import pdb 8 | 9 | from lxml import etree 10 | import json 11 | import dill 12 | 13 | from suite.dtype.primitive import ConstraintString, NonNumericString 14 | from suite.dtype.primitive import ConstantString 15 | from suite.dtype.primitive import PrimitiveMaker 16 | from suite.io.iprimitive import ConstraintStringIo 17 | from suite.io.iprimitive import ConstantStringIo 18 | from suite.io.iprimitive import NonNumericStringIo 19 | from suite.io.icontainer import PairIo 20 | 21 | from suite.dtype.container import Pair 22 | from suite.dtype.container import ContainerMaker 23 | 24 | 25 | class TestIoContainer(unittest.TestCase): 26 | "test io container module" 27 | 28 | def setUp(self): 29 | "setup small data" 30 | 31 | def lfn(x: ConstantString): 32 | return x.constr.islower() 33 | 34 | pmakerc = PrimitiveMaker("constraint string") 35 | pmakercons = PrimitiveMaker("constant string") 36 | nnmaker = PrimitiveMaker("non numeric string") 37 | cmaker = ContainerMaker 38 | self.constrio = ConstraintStringIo 39 | self.consio = ConstantStringIo 40 | 41 | self.mycstr = pmakerc.from_string(mystr="my valid constraint string", fnc=lfn) 42 | self.myfn = dill.dumps(self.mycstr.fn) 43 | self.mynnstr = nnmaker.from_string(mystr="my valid non numeric string") 44 | self.mynnfn = dill.dumps(self.mynnstr.fn) 45 | self.parg1 = pmakercons.make(mystr="mystr1") 46 | self.parg2 = pmakercons.make(mystr="mystr2") 47 | self.pair = cmaker.from_type(objType=Pair, arg1=self.parg1, arg2=self.parg2) 48 | mystr1 = pmakerc.from_string(mystr="my true string", fnc=lfn) 49 | mystr2 = pmakerc.from_string(mystr="my another true string", fnc=lfn) 50 | self.scpair1 = cmaker.from_type( 51 | objType=SingleConstraintPair, arg1=mystr1, arg2=mystr2 52 | ) 53 | 54 | def test_pair_io_to_xml(self): 55 | "test pair io" 56 | pio = PairIo(self.pair) 57 | ionst = pio.getIoInstance("xml") 58 | el1 = ionst.to_element() 59 | el = etree.Element("pair") 60 | el.set("class", Pair.__name__) 61 | xmlio = self.consio.getIoClass("xml") 62 | el.append(xmlio(self.parg1).to_element()) 63 | el.append(xmlio(self.parg2).to_element()) 64 | self.assertEqual(el.tag, el1.tag) 65 | self.assertEqual(el.text, el1.text) 66 | self.assertEqual(el.attrib, el1.attrib) 67 | 68 | def test_pair_io_to_json(self): 69 | "test pair io" 70 | pio = PairIo(self.pair) 71 | ionst = pio.getIoInstance("json") 72 | el1 = ionst.to_json() 73 | mydict = ionst.to_dict() 74 | pdict = {} 75 | pdict["class"] = Pair.__name__ 76 | pdict["type"] = "pair" 77 | jsio = self.consio.getIoClass("json") 78 | member1 = jsio(self.parg1).to_dict() 79 | member2 = jsio(self.parg2).to_dict() 80 | pdict["members"] = [member1, member2] 81 | pjson = json.dumps(pdict, ensure_ascii=True, indent=2, sort_keys=True) 82 | self.assertEqual(pjson, el1) 83 | self.assertEqual(pdict, mydict) 84 | 85 | 86 | # def test_pair_io_from_xml(self): 87 | # "test pair io" 88 | # pio = PairIo(self.pair) 89 | # ionst = pio.getIoClass("xml") 90 | # el = etree.Element("pair") 91 | # el.set("class", Pair.__name__) 92 | # subel1 = etree.Element("member") 93 | # subel2 = etree.Element("member") 94 | # members = etree.Element("members") 95 | # subel1.text = self.pair.arg1 96 | # subel2.text = self.pair.arg2 97 | # members.append(subel1) 98 | # members.append(subel2) 99 | # el.append(members) 100 | 101 | # el1 = ionst.from_element(el) 102 | # self.assertEqual(el1, self.pair) 103 | 104 | # def test_pair_io_from_json(self): 105 | # "test pair io" 106 | # pio = PairIo(self.pair) 107 | # ionst = pio.getIoClass("json") 108 | # pdict = {} 109 | # pdict["class"] = Pair.__name__ 110 | # pdict["type"] = "pair" 111 | # member1 = {} 112 | # member2 = {} 113 | # member1["type"] = "member" 114 | # member1["value"] = self.pair.arg1 115 | # member2["type"] = "member" 116 | # member2["value"] = self.pair.arg2 117 | # pdict["members"] = [member1, member2] 118 | # pjson = json.dumps(pdict, ensure_ascii=True, indent=2, sort_keys=True) 119 | # mypair = ionst.from_json(pjson) 120 | # self.assertEqual(mypair, self.pair) 121 | 122 | # def test_single_constraint_pair_io_to_xml(self): 123 | # "test pair io" 124 | 125 | # def arg2element(myarg): 126 | # "" 127 | # cstrio = ConstraintStringIo(myarg) 128 | # ioinst = cstrio.getIoInstance("xml") 129 | # el = ioinst.to_element() 130 | # return el 131 | 132 | # pio = SingleConstraintPairIo(self.scpair1) 133 | # ionst = pio.getIoInstance("xml") 134 | # el1 = ionst.to_element() 135 | # el = etree.Element("pair") 136 | # el.set("class", SingleConstraintPair.__name__) 137 | # myfn = dill.dumps(self.scpair1.constfn) 138 | # el.set("constraint", myfn.hex()) 139 | 140 | # members = etree.Element("members") 141 | # sel1 = arg2element(self.scpair1.arg1) 142 | # sel2 = arg2element(self.scpair1.arg2) 143 | # members.append(sel1) 144 | # members.append(sel2) 145 | # el.append(members) 146 | # self.assertEqual(el.tag, el1.tag) 147 | # self.assertEqual(el.text, el1.text) 148 | # self.assertEqual(el.attrib, el1.attrib) 149 | 150 | # def test_single_constraint_pair_io_to_json(self): 151 | # "test pair io" 152 | 153 | # def arg2dict(myarg): 154 | # "" 155 | # cstrio = ConstraintStringIo(myarg) 156 | # ioinst = cstrio.getIoInstance("json") 157 | # return ioinst.to_dict() 158 | 159 | # pio = SingleConstraintPairIo(self.scpair1) 160 | # ionst = pio.getIoInstance("json") 161 | # el1 = ionst.to_json() 162 | # mydict = ionst.to_dict() 163 | # pdict = {} 164 | # pdict["class"] = SingleConstraintPair.__name__ 165 | # pdict["type"] = "pair" 166 | # myfn = dill.dumps(self.scpair1.constfn) 167 | # pdict["constraint"] = myfn.hex() 168 | # member1 = arg2dict(self.scpair1.arg1) 169 | # member2 = arg2dict(self.scpair1.arg2) 170 | # pdict["members"] = [member1, member2] 171 | # pjson = json.dumps(pdict, ensure_ascii=False, indent=2, sort_keys=True) 172 | # self.assertEqual(pdict, mydict) 173 | # self.assertEqual(pjson, el1) 174 | 175 | # def test_single_constraint_pair_io_from_xml(self): 176 | # "test pair io" 177 | 178 | # def arg2element(myarg): 179 | # "" 180 | # cstrio = ConstraintStringIo(myarg) 181 | # ioinst = cstrio.getIoInstance("xml") 182 | # el = ioinst.to_element() 183 | # return el 184 | 185 | # pio = SingleConstraintPairIo 186 | # ionst = pio.getIoClass("xml") 187 | # el = etree.Element("pair") 188 | # el.set("class", SingleConstraintPair.__name__) 189 | # myfn = dill.dumps(self.scpair1.constfn) 190 | # el.set("constraint", myfn.hex()) 191 | # members = etree.Element("members") 192 | # e1 = arg2element(self.scpair1.arg1) 193 | # e2 = arg2element(self.scpair1.arg2) 194 | # members.append(e1) 195 | # members.append(e2) 196 | # el.append(members) 197 | # el1 = ionst.from_element(el) 198 | # self.assertEqual(el1, self.scpair1) 199 | 200 | # def test_single_constraint_pair_io_from_json(self): 201 | # "test pair io" 202 | 203 | # def arg2dict(myarg): 204 | # "" 205 | # cstrio = ConstraintStringIo(myarg) 206 | # ioinst = cstrio.getIoInstance("json") 207 | # return ioinst.to_dict() 208 | 209 | # pio = SingleConstraintPairIo 210 | # ionst = pio.getIoClass("json") 211 | # pdict = {} 212 | # pdict["class"] = SingleConstraintPair.__name__ 213 | # pdict["type"] = "pair" 214 | # member1 = arg2dict(self.scpair1.arg1) 215 | # member2 = arg2dict(self.scpair1.arg2) 216 | # pdict["members"] = [member1, member2] 217 | # pjson = json.dumps(pdict, ensure_ascii=True, indent=2, sort_keys=True) 218 | # mypair = ionst.from_json(pjson) 219 | # self.assertEqual(mypair, self.scpair1) 220 | 221 | 222 | if __name__ == "__main__": 223 | unittest.main() 224 | -------------------------------------------------------------------------------- /suite/dtype/primitive.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains primitives of the suite 3 | """ 4 | # author: Kaan Eraslan 5 | # license: see, LICENSE 6 | 7 | from fractions import Fraction 8 | 9 | from typing import NamedTuple 10 | from types import FunctionType 11 | 12 | 13 | class BasePrimitive: 14 | "Base class for all primitives" 15 | 16 | def __ne__(self, other): 17 | res = self.__eq__(other) 18 | if res is not NotImplemented: 19 | return not res 20 | return NotImplemented 21 | 22 | def __hash__(self): 23 | items = list(self.__dict__.items()) 24 | items.sort() 25 | return hash(tuple(items)) 26 | 27 | 28 | class ConstantStringBase(NamedTuple): 29 | "Models spec primitive Constant String" 30 | constr: str 31 | 32 | 33 | class ConstantString(BasePrimitive, ConstantStringBase): 34 | def isValid(self): 35 | "Check if constant string is valid" 36 | check = True 37 | try: 38 | mybyte = self.constr.encode("utf-8") 39 | mystr = mybyte.decode("utf-8") 40 | check = mystr == self.constr 41 | except UnicodeDecodeError or UnicodeEncodeError: 42 | check = False 43 | return check 44 | 45 | def __repr__(self): 46 | return "string: " + str(self) + " of type: " + self.__class__.__name__ 47 | 48 | def __str__(self): 49 | return self.constr 50 | 51 | def __eq__(self, other): 52 | if isinstance(other, ConstantString): 53 | cond1 = other.constr == self.constr 54 | return cond1 55 | return NotImplemented 56 | 57 | def __copy__(self): 58 | return ConstantString(constr=self.constr) 59 | 60 | def __hash__(self): 61 | items = list(self.__dict__.items()) 62 | items.sort() 63 | return hash(tuple(items)) 64 | 65 | 66 | class ConstraintStringBase(NamedTuple): 67 | cstr: ConstantString 68 | fn: FunctionType 69 | 70 | def isValid(self): 71 | "Is string valid for given constraint" 72 | return self.fn(self.cstr) 73 | 74 | def __str__(self): 75 | return str(self.cstr) 76 | 77 | def __repr__(self): 78 | mess = "string: " + str(self) + " of type: " + self.__class__.__name__ 79 | mess += " with constraint: " + self.fn.__name__ 80 | return mess 81 | 82 | def __eq__(self, other): 83 | if isinstance(other, ConstraintString): 84 | cond1 = other.cstr == self.cstr 85 | cond2 = other.fn.__name__ == self.fn.__name__ 86 | return cond1 and cond2 87 | return NotImplemented 88 | 89 | 90 | class ConstraintString(BasePrimitive, ConstraintStringBase): 91 | def __copy__(self): 92 | return ConstraintString(cstr=self.cstr, fn=self.fn) 93 | 94 | def __hash__(self): 95 | items = list(self.__dict__.items()) 96 | items.sort() 97 | return hash(tuple(items)) 98 | 99 | 100 | def nonNumeric(myx: ConstantString) -> bool: 101 | x = myx.constr 102 | try: 103 | mystr = int(x) 104 | except ValueError: 105 | try: 106 | mystr = float(x) 107 | except ValueError: 108 | try: 109 | mystr = Fraction(x) 110 | except ValueError: 111 | try: 112 | mystr = complex(x) 113 | except ValueError: 114 | mystr = x 115 | return not isinstance(mystr, (float, int, complex, Fraction)) 116 | 117 | 118 | class NonNumericStringBase(NamedTuple): 119 | cstr: ConstantString 120 | 121 | @property 122 | def fn(self): 123 | return nonNumeric 124 | 125 | def isValid(self): 126 | "Is string valid for given constraint" 127 | return self.fn(self.cstr) 128 | 129 | def __str__(self): 130 | return str(self.cstr) 131 | 132 | def __repr__(self): 133 | mess = "string: " + str(self) + " of type: " + self.__class__.__name__ 134 | mess += " with constraint: " + self.fn.__name__ 135 | return mess 136 | 137 | 138 | class NonNumericString(NonNumericStringBase, BasePrimitive): 139 | "Models spec primitive Non Numeric String" 140 | 141 | def __eq__(self, other): 142 | if isinstance(other, NonNumericString): 143 | cond1 = other.cstr == self.cstr 144 | cond2 = other.fn.__name__ == self.fn.__name__ 145 | return cond1 and cond2 146 | return NotImplemented 147 | 148 | def __copy__(self): 149 | return NonNumericString(cstr=self.cstr, fn=self.fn) 150 | 151 | def __hash__(self): 152 | items = list(self.__dict__.items()) 153 | items.sort() 154 | return hash(tuple(items)) 155 | 156 | 157 | class PrimitiveMaker: 158 | "" 159 | 160 | def __init__(self, choice: str): 161 | self.choice = choice 162 | 163 | @classmethod 164 | def make_constant_string(cls, mystr: str): 165 | mess = "Incompatible type: " + type(mystr).__name__ 166 | mess += ". Only str type is allowed" 167 | if not isinstance(mystr, str): 168 | raise TypeError(mess) 169 | constr = ConstantString(constr=mystr) 170 | if not constr.isValid(): 171 | raise ValueError("Not valid ConstantString: " + str(constr)) 172 | return constr 173 | 174 | @classmethod 175 | def make_constraint_string(cls, mystr: ConstantString, fnc: FunctionType): 176 | mess = "Incompatible type: " + type(mystr).__name__ 177 | mess += ". Only ConstantString type is allowed" 178 | if not isinstance(mystr, ConstantString): 179 | raise TypeError(mess) 180 | mess = "Incompatible type: " + type(fnc).__name__ 181 | mess += ". Only FunctionType type is allowed" 182 | if not isinstance(fnc, FunctionType): 183 | raise TypeError(mess) 184 | fname = fnc.__name__ 185 | mess = "Anonymous functions are not allowed." 186 | if not fname != "": 187 | raise ValueError(mess) 188 | cstr = ConstraintString(cstr=mystr, fn=fnc) 189 | if not cstr.isValid(): 190 | raise ValueError("Invalid ConstraintString: " + str(cstr)) 191 | return cstr 192 | 193 | @classmethod 194 | def make_non_numeric_string(cls, mystr: ConstantString): 195 | mess = "Incompatible type: " + type(mystr).__name__ 196 | mess += ". Only ConstantString type is allowed" 197 | if not isinstance(mystr, ConstantString): 198 | raise TypeError(mess) 199 | nnstr = NonNumericString(cstr=mystr) 200 | if not nnstr.isValid(): 201 | raise ValueError("Not valid NonNumericString: " + str(nnstr)) 202 | return nnstr 203 | 204 | def make(self, **kwargs): 205 | choice = self.choice.lower() 206 | if choice == "constant string": 207 | return self.make_constant_string(**kwargs) 208 | elif choice == "constraint string": 209 | return self.make_constraint_string(**kwargs) 210 | elif choice == "non numeric string": 211 | return self.make_non_numeric_string(**kwargs) 212 | else: 213 | raise ValueError("Unknown primitive choice: " + choice) 214 | 215 | def from_string(self, mystr: str, **kwargs): 216 | "make object from choice using string" 217 | choice = self.choice.lower() 218 | mess = "Incompatible type: " + type(mystr).__name__ 219 | mess += ". Only str type is allowed" 220 | if not isinstance(mystr, str): 221 | raise TypeError(mess) 222 | if choice == "constant string": 223 | return self.make_constant_string(mystr) 224 | elif choice == "constraint string": 225 | cstr = self.make_constant_string(mystr) 226 | return self.make_constraint_string(mystr=cstr, **kwargs) 227 | elif choice == "non numeric string": 228 | cstr = self.make_constant_string(mystr) 229 | return self.make_non_numeric_string(mystr=cstr) 230 | else: 231 | raise ValueError("Unknown primitive choice: " + choice) 232 | 233 | @classmethod 234 | def from_type(cls, primitiveType, **kwargs): 235 | "make primitive from giving its type" 236 | pname = primitiveType.__name__ 237 | if pname == "ConstantString": 238 | return cls.make_constant_string(**kwargs) 239 | elif pname == "ConstraintString": 240 | return cls.make_constraint_string(**kwargs) 241 | elif pname == "NonNumericString": 242 | return cls.make_non_numeric_string(**kwargs) 243 | else: 244 | raise ValueError("Unknown Primitive Type: " + pname) 245 | -------------------------------------------------------------------------------- /suite/io/icontainer.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: io for containers in multiple formats 4 | 5 | from suite.dtype.primitive import ConstraintString 6 | from suite.dtype.primitive import NonNumericString 7 | from suite.dtype.primitive import ConstantString 8 | from suite.dtype.primitive import PrimitiveMaker 9 | 10 | from suite.dtype.container import Pair, Array 11 | 12 | from suite.io.iprimitive import ConstantStringIo 13 | from suite.io.iprimitive import ConstraintStringIo 14 | from suite.io.iprimitive import NonNumericStringIo 15 | 16 | from lxml import etree 17 | import json 18 | import dill 19 | from typing import List, Dict 20 | 21 | 22 | def dict_dump(mdict: dict): 23 | "dump dictionaries in homogeneous fashion" 24 | return json.dumps(mdict, ensure_ascii=False, indent=2, sort_keys=True) 25 | 26 | 27 | class _ContainerIo: 28 | "Base Class for all container io" 29 | 30 | def __init__(self, container, containerType): 31 | 32 | check = True 33 | try: 34 | citer = iter(container) 35 | except TypeError: 36 | check = False 37 | # 38 | if not check: 39 | raise ValueError("Container is not iterable") 40 | if not isinstance(container, containerType): 41 | raise TypeError( 42 | "Container:" 43 | + str(container) 44 | + " is not an instance of given container type: " 45 | + containerType.__name__ 46 | ) 47 | self.container = container 48 | self.containerType = containerType 49 | 50 | @classmethod 51 | def check_value_error(cls, objval: str, wantedVal: str, messPrefix: str): 52 | "Check if given object value correspond to wanted value" 53 | if objval != wantedVal: 54 | raise ValueError(messPrefix + objval + " it must be: " + wantedVal) 55 | return 56 | 57 | def add_members_to_parent(self, parent, members): 58 | "add members to parent set" 59 | for member in members: 60 | parent.append(member) 61 | return parent 62 | 63 | def member_to_dict(self, member) -> dict: 64 | "transform member to dict" 65 | raise NotImplementedError 66 | 67 | def dict_to_unit(self, mdict: dict): 68 | "transform dictionary to given serialization unit" 69 | raise NotImplementedError 70 | 71 | def member_to_unit(self, member): 72 | "transform member to serialization unit" 73 | mdict = self.member_to_dict(member) 74 | unit = self.dict_to_unit(mdict) 75 | return unit 76 | 77 | @classmethod 78 | def unit_to_dict(cls, unit): 79 | "transform serialization unit to dictionary" 80 | raise NotImplementedError 81 | 82 | @classmethod 83 | def dict_to_member(cls, mdict: dict): 84 | "transform dictionary to member type" 85 | raise NotImplementedError 86 | 87 | @classmethod 88 | def unit_to_member(cls, unit): 89 | "transform serialization unit into member type" 90 | mdict = cls.unit_to_dict(unit) 91 | member = cls.dict_to_member(mdict) 92 | return member 93 | 94 | @classmethod 95 | def check_container_unit(cls, cunit) -> bool: 96 | "check if given serialization unit conforms to expected container" 97 | raise NotImplementedError 98 | 99 | def to_dict(self) -> dict: 100 | "transform serialization container unit dictionary" 101 | raise NotImplementedError 102 | 103 | 104 | class _ContainerXmlIo(_ContainerIo): 105 | "Container input output" 106 | cmaker = ContainerMaker("") 107 | pmaker = PrimitiveMaker("") 108 | primitiveIo = ConstantStringIo.getIoClass("xml") 109 | 110 | def __init__(self, container, containerType): 111 | super().__init__(container, containerType) 112 | 113 | @classmethod 114 | def element_to_dict(cls, el: etree.Element) -> dict: 115 | "transform element to dict" 116 | eldict = el.attrib 117 | eldict["text"] = el.text 118 | eldict["tag"] = el.tag 119 | return eldict 120 | 121 | @classmethod 122 | def unit_to_member(cls, el: etree.Element): 123 | "transform element to member" 124 | return cls.primitiveIo.from_element(el) 125 | 126 | def member_to_unit(self, member: ConstantString) -> etree.Element: 127 | "transform member to element" 128 | consio = self.primitiveIo(member) 129 | return consio.to_element() 130 | 131 | def member_to_dict(self, member: ConstantString) -> dict: 132 | "transform member to element" 133 | consio = self.primitiveIo(member) 134 | return consio.to_dict() 135 | 136 | 137 | class _ContainerJsonIo(_ContainerIo): 138 | "Container input output json" 139 | cmaker = ContainerMaker("") 140 | pmaker = PrimitiveMaker("") 141 | primitiveIo = ConstantStringIo.getIoClass("json") 142 | 143 | def __init__(self, container, containerType): 144 | super().__init__(container, containerType) 145 | 146 | @classmethod 147 | def json_to_dict(cls, jsonstr: str) -> dict: 148 | return json.loads(jsonstr) 149 | 150 | def member_to_unit(self, member: ConstantString) -> dict: 151 | consio = self.primitiveIo(member) 152 | return consio.to_dict() 153 | 154 | @classmethod 155 | def unit_to_member(cls, cdict: dict): 156 | return cls.primitiveIo.from_dict(cdict) 157 | 158 | def to_json(self): 159 | return dict_dump(self.to_dict()) 160 | 161 | def toJSON(self): 162 | return self.to_json() 163 | 164 | 165 | class _ContainerIoBuilder: 166 | "Generic io builder using supported formats" 167 | SUPPORTED = ["xml", "json"] 168 | 169 | def __init__(self, container): 170 | if not container.isValid(): 171 | raise ValueError("Container: " + str(container) + " is not valid") 172 | self.container = container 173 | 174 | class XmlIo(_ContainerXmlIo): 175 | pass 176 | 177 | class JsonIo(_ContainerJsonIo): 178 | pass 179 | 180 | @classmethod 181 | def getIoClass(cls, render_format: str): 182 | render_format = render_format.lower() 183 | if render_format == cls.SUPPORTED[0]: 184 | return cls.XmlIo 185 | elif render_format == cls.SUPPORTED[1]: 186 | return cls.JsonIo 187 | else: 188 | raise ValueError( 189 | render_format + " not in supported formats: " + ",".join(cls.SUPPORTED) 190 | ) 191 | 192 | def __str__(self): 193 | return ( 194 | "IO builder for " 195 | + self.container.__class__.__name__ 196 | + " " 197 | + str(self.container) 198 | ) 199 | 200 | def __repr__(self): 201 | return ( 202 | "IO builder for " 203 | + self.container.__class__.__name__ 204 | + " " 205 | + repr(self.container) 206 | ) 207 | 208 | 209 | class PairIo(_ContainerIoBuilder): 210 | "Pair io" 211 | 212 | def __init__(self, pair: Pair): 213 | super().__init__(pair) 214 | 215 | class XmlIo(_ContainerXmlIo): 216 | "pair xml io" 217 | 218 | cmaker = ContainerMaker("pair") 219 | pmaker = PrimitiveMaker(choice="constant string") 220 | primitiveIo = ConstantStringIo.getIoClass("xml") 221 | 222 | def __init__(self, pair): 223 | super().__init__(pair, Pair) 224 | 225 | def to_element(self): 226 | "transform pair to xml" 227 | str1 = self.container.arg1 228 | str2 = self.container.arg2 229 | el = etree.Element("pair") 230 | el.set("class", self.containerType.__name__) 231 | el1 = self.member_to_unit(str1) 232 | el2 = self.member_to_unit(str2) 233 | self.add_members_to_parent(el, [el1, el2]) 234 | return el 235 | 236 | def to_dict(self) -> dict: 237 | "to dict pair" 238 | str1 = self.container.arg1 239 | str2 = self.container.arg2 240 | pdict = {} 241 | pdict["class"] = self.containerType.__name__ 242 | pdict["type"] = "pair" 243 | member1 = self.member_to_dict(str1) 244 | member2 = self.member_to_dict(str2) 245 | pdict["members"] = [] 246 | self.add_members_to_parent(pdict["members"], [member1, member2]) 247 | return pdict 248 | 249 | @classmethod 250 | def from_element(cls, el: etree.Element): 251 | "Obtain pair from element" 252 | eltag = el.tag 253 | elclass = el.get("class") 254 | cls.check_value_error(eltag, "pair", "Given element tag: ") 255 | cls.check_value_error(elclass, "Pair", "Given element class: ") 256 | arg1 = cls.unit_to_member(el[0]) 257 | arg2 = cls.unit_to_member(el[1]) 258 | pair = cls.cmaker.make(arg1=arg1, arg2=arg2) 259 | return pair 260 | 261 | class JsonIo(_ContainerJsonIo): 262 | "pair json io" 263 | cmaker = ContainerMaker("pair") 264 | pmaker = PrimitiveMaker(choice="constant string") 265 | primitiveIo = ConstantStringIo.getIoClass("json") 266 | 267 | def __init__(self, pair): 268 | super().__init__(pair, Pair) 269 | 270 | def to_dict(self) -> dict: 271 | "to dict pair" 272 | str1 = self.container.arg1 273 | str2 = self.container.arg2 274 | pdict = {} 275 | pdict["class"] = self.containerType.__name__ 276 | pdict["type"] = "pair" 277 | member1 = self.member_to_unit(str1) 278 | member2 = self.member_to_unit(str2) 279 | pdict["members"] = [] 280 | self.add_members_to_parent(pdict["members"], [member1, member2]) 281 | return pdict 282 | 283 | @classmethod 284 | def from_json(cls, jsonstr: str): 285 | "obtain pair from json object" 286 | objdict = json.loads(jsonstr) 287 | return cls.from_dict(objdict) 288 | 289 | @classmethod 290 | def from_dict(cls, cdict: dict): 291 | "obtain pair from dict" 292 | objtype = cdict.get("type", "") 293 | objclass = cdict.get("class", "") 294 | cls.check_value_error(objtype, "pair", "Given object type: ") 295 | cls.check_value_error(objclass, "Pair", "Given object class: ") 296 | members = cdict["members"] 297 | member1 = cls.unit_to_member(members[0]) 298 | member2 = cls.unit_to_member(members[1]) 299 | pair = cls.cmaker.make(arg1=member1, arg2=member2) 300 | return pair 301 | 302 | def getIoInstance(self, render_format: str): 303 | "io for pair given format" 304 | return self.getIoClass(render_format)(self.container) 305 | 306 | 307 | -------------------------------------------------------------------------------- /tests/test_edition.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: test scripts of suite 4 | 5 | import unittest 6 | 7 | from suite import idchecker as idc 8 | from suite import projectMaker as pjm 9 | from suite import validator as vd 10 | 11 | import os 12 | import json 13 | import shutil 14 | import pdb 15 | 16 | 17 | class TestDigitalEditionSuite(unittest.TestCase): 18 | "test edition" 19 | 20 | def setUp(self): 21 | "set up" 22 | self.currentdir = os.path.abspath(os.curdir) 23 | self.testdir = os.path.join(self.currentdir, "tests") 24 | self.assetdir = os.path.join(self.testdir, "assets") 25 | self.project_parent = os.path.join(self.testdir, "sampleProjectParent") 26 | self.project_name = "sampleProject" 27 | self.project_path = os.path.join(self.project_parent, 28 | self.project_name) 29 | self.simple_doc1_path = os.path.join(self.assetdir, "simple1.json") 30 | self.simple_doc2_path = os.path.join(self.assetdir, "simple2.json") 31 | self.simple_doc3_path = os.path.join(self.assetdir, "simple3.json") 32 | self.combined_doc_path = os.path.join(self.assetdir, "combined1.json") 33 | self.predicate_doc_path = os.path.join( 34 | self.assetdir, "predicate1.json") 35 | self.entity_doc_path = os.path.join(self.assetdir, "entity1.json") 36 | self.link_doc_path = os.path.join(self.assetdir, "link1.json") 37 | 38 | def test_assert_first_not_second_proc(self): 39 | "" 40 | noneval = pjm.assert_first_not_second_proc(self.project_parent, 41 | self.project_path) 42 | self.assertEqual(noneval, None) 43 | 44 | def test_make_project_dirs(self): 45 | "test make project dirs" 46 | asset_dir_cmp = os.path.join(self.project_path, "assets") 47 | predicate_dir_cmp = os.path.join(asset_dir_cmp, "predicate") 48 | link_dir_cmp = os.path.join(asset_dir_cmp, "link") 49 | entity_dir_cmp = os.path.join(asset_dir_cmp, "entity") 50 | authority_dir_cmp = os.path.join(predicate_dir_cmp, "authority") 51 | simple_dir_cmp = os.path.join(authority_dir_cmp, "simple") 52 | (simple_dir, author_dir, 53 | entity_dir, link_dir, predicate_dir) = pjm.mk_project_dirs( 54 | self.project_parent, 55 | self.project_name) 56 | self.assertEqual(entity_dir_cmp, entity_dir) 57 | self.assertEqual(author_dir, authority_dir_cmp) 58 | self.assertEqual(simple_dir, simple_dir_cmp) 59 | self.assertEqual(link_dir, link_dir_cmp) 60 | self.assertEqual(predicate_dir_cmp, predicate_dir) 61 | cond = bool(os.path.isdir(entity_dir) and 62 | os.path.isdir(author_dir) and 63 | os.path.isdir(link_dir) and 64 | os.path.isdir(simple_dir) and 65 | os.path.isdir(predicate_dir)) 66 | self.assertTrue(cond) 67 | shutil.rmtree(self.project_path) 68 | 69 | def test_make_sample_files(self): 70 | "make sample files" 71 | (simple_dir, author_dir, 72 | entity_dir, link_dir, predicate_dir) = pjm.mk_project_dirs( 73 | self.project_parent, 74 | self.project_name) 75 | pjm.make_samples_proc(simple_dir, author_dir, 76 | predicate_dir, entity_dir, link_dir) 77 | link_path = os.path.join(link_dir, "sampleEntityPredicateLink.json") 78 | entity_path = os.path.join(entity_dir, "sampleEntityRelations.json") 79 | predicate_path = os.path.join(predicate_dir, "samplePredicate.json") 80 | author_path = os.path.join(author_dir, "sampleCombinedSimple.json") 81 | simple_path = os.path.join(simple_dir, "sampleSimple.json") 82 | cond = bool(os.path.isfile(simple_path) and 83 | os.path.isfile(author_path) and 84 | os.path.isfile(predicate_path) and 85 | os.path.isfile(entity_path) and 86 | os.path.isfile(link_path)) 87 | self.assertTrue(cond) 88 | shutil.rmtree(self.project_path) 89 | 90 | def test_read_json(self): 91 | "test read json" 92 | (simple_dir, author_dir, 93 | entity_dir, link_dir, predicate_dir) = pjm.mk_project_dirs( 94 | self.project_parent, 95 | self.project_name) 96 | pjm.make_samples_proc(simple_dir, author_dir, 97 | predicate_dir, entity_dir, link_dir) 98 | link_path = os.path.join(link_dir, "sampleEntityPredicateLink.json") 99 | cmp_link = { 100 | "sample-entity-1": { 101 | "sample-relation-3": {"0": "sample-predicate-1"} 102 | }, 103 | "sample-entity-2": { 104 | "sample-relation-3": {"0": "sample-predicate-2"} 105 | } 106 | } 107 | link_doc = vd.read_json(link_path) 108 | self.assertEqual(cmp_link, link_doc) 109 | shutil.rmtree(self.project_path) 110 | 111 | def test_get_keys_array_from_object(self): 112 | "" 113 | array_obj = {"myval": "another value", 114 | "my1": {"0": "sample-1", "1": "sample-2"}, 115 | "my2": {"0": "sample-3", "1": "sample-6"}, 116 | "my3": {"0": "sample-7", "1": "sample-2", "2": "sample-9"} 117 | } 118 | cmp_obj = {"my1": {"0": "sample-1", "1": "sample-2"}, 119 | "my2": {"0": "sample-3", "1": "sample-6"}, 120 | "my3": {"0": "sample-7", "1": "sample-2", "2": "sample-9"} 121 | } 122 | array_obj = vd.get_keys_array_from_object(array_obj) 123 | self.assertEqual(array_obj, cmp_obj) 124 | 125 | def test_check_key_value_string(self): 126 | "" 127 | self.assertEqual(True, 128 | vd.check_key_value_string("my1", "value")) 129 | self.assertEqual(False, 130 | vd.check_key_value_string(0, "value")) 131 | 132 | def test_key_value_string_object(self): 133 | "" 134 | testobj1 = {"my": "sample-7", "string": "sample-2", "key": "sample-9"} 135 | testobj2 = {"0": "sample-7", "1": "sample-2", "2": "sample-9"} 136 | testobj3 = {0: "sample-7", 1: "sample-2", "2": "sample-9"} 137 | self.assertEqual(True, vd.check_key_value_string_object(testobj1)) 138 | self.assertEqual(False, vd.check_key_value_string_object(testobj2)) 139 | self.assertEqual(False, vd.check_key_value_string_object(testobj3)) 140 | 141 | def test_check_key_value_int(self): 142 | "" 143 | self.assertEqual(True, 144 | vd.check_key_value_int("0", "value")) 145 | self.assertEqual(False, 146 | vd.check_key_value_int(0, "value")) 147 | self.assertEqual(False, 148 | vd.check_key_value_int("mykey", "value")) 149 | self.assertEqual(False, 150 | vd.check_key_value_int("0", 25)) 151 | 152 | def test_check_key_value_int_object(self): 153 | "" 154 | test_obj1 = {"0": "sample-1", "1": "sample-2", "3": "sample-9"} 155 | test_obj2 = {"for": "sample-1", "1": "sample-2", "3": "sample-9"} 156 | self.assertEqual(True, vd.check_key_value_int_object(test_obj1)) 157 | self.assertEqual(False, vd.check_key_value_int_object(test_obj2)) 158 | 159 | def test_check_key_key_value_int_object(self): 160 | "" 161 | test_obj1 = { 162 | "my3": {"0": "sample-7", "1": "sample-2", "2": "sample-9"} 163 | } 164 | test_obj2 = { 165 | "my2": {"for": "sample-3", "1": "sample-6"}, 166 | } 167 | for key, val in test_obj1.items(): 168 | self.assertEqual(True, 169 | vd.check_key_key_value_int_object(key, val)) 170 | for key, val in test_obj2.items(): 171 | self.assertEqual(False, 172 | vd.check_key_key_value_int_object(key, val)) 173 | 174 | def test_check_key_key_value_string_object(self): 175 | "" 176 | test_obj1 = { 177 | "my3": {"my": "sample-7", "key": "sample-2", "here": "sample-9"} 178 | } 179 | test_obj2 = { 180 | "my2": {0: "sample-3", "1": "sample-6"}, 181 | } 182 | test_obj3 = { 183 | "my2": {"0": "sample-3", "1": "sample-6"}, 184 | } 185 | for key, val in test_obj1.items(): 186 | self.assertEqual(True, 187 | vd.check_key_key_value_string_object(key, val)) 188 | # 189 | for key, val in test_obj2.items(): 190 | self.assertEqual(False, 191 | vd.check_key_key_value_string_object(key, val)) 192 | # 193 | for key, val in test_obj3.items(): 194 | self.assertEqual(False, 195 | vd.check_key_key_value_string_object(key, val)) 196 | # 197 | 198 | def test_validate_simple_authority_structure(self): 199 | "" 200 | doc1 = vd.read_json(self.simple_doc1_path) 201 | doc2 = vd.read_json(self.simple_doc2_path) 202 | doc3 = vd.read_json(self.simple_doc3_path) 203 | check1 = vd.validate_simple_authority_structure(doc1) 204 | check2 = vd.validate_simple_authority_structure(doc2) 205 | check3 = vd.validate_simple_authority_structure(doc3) 206 | self.assertEqual(check1[0], True) 207 | self.assertEqual(check2[0], True) 208 | self.assertEqual(check3[0], True) 209 | 210 | def test_validate_combined_authority_structure(self): 211 | "" 212 | doc1 = vd.read_json(self.combined_doc_path) 213 | check = vd.validate_combined_authority_structure(doc1) 214 | self.assertEqual(check[0], True) 215 | 216 | def test_validate_predicate_entity_link_structure(self): 217 | "" 218 | doc1 = vd.read_json(self.predicate_doc_path) 219 | doc2 = vd.read_json(self.entity_doc_path) 220 | doc3 = vd.read_json(self.link_doc_path) 221 | check1 = vd.validate_entity_predicate_structure(doc1) 222 | check2 = vd.validate_entity_predicate_structure(doc2) 223 | check3 = vd.validate_entity_predicate_structure(doc3) 224 | self.assertEqual(check1[0], True) 225 | self.assertEqual(check2[0], True) 226 | self.assertEqual(check3[0], True) 227 | 228 | def test_validate_combined_authority_content(self): 229 | "" 230 | doc1 = vd.read_json(self.combined_doc_path) 231 | simples1 = ["sample-relation-2", 232 | "sample-grammar-1", 233 | "sample-grammar-6", 234 | "sample-grammar-2"] 235 | simples2 = ["sample-relation-2", 236 | "sample-grammar-5", 237 | "sample-grammar-6", 238 | "sample-grammar-2"] 239 | check1 = vd.validate_combined_authority_content(doc1, simples1) 240 | check2 = vd.validate_combined_authority_content(doc1, simples2) 241 | self.assertEqual(True, check1[0]) 242 | self.assertEqual(False, check2[0]) 243 | 244 | def test_validate_predicate_entity_content(self): 245 | "" 246 | doc1 = vd.read_json(self.predicate_doc_path) 247 | ids1 = ["sample-relation-3", "sample-combined-grammar-2", 248 | "sample-grammar-5", 249 | "sample-grammar-7", 250 | "sample-relation-1", 251 | "sample-word-3"] 252 | ids2 = ["sample-relation-3", "sample-combined-grammar-2", 253 | "sample-grammar-8", 254 | "sample-grammar-7", 255 | "sample-relation-5", 256 | "sample-word-6"] 257 | check1 = vd.validate_entity_predicate_content(doc1, ids1) 258 | check2 = vd.validate_entity_predicate_content(doc1, ids2) 259 | self.assertEqual(True, check1[0]) 260 | self.assertEqual(False, check2[0]) 261 | 262 | def test_validate_link_content(self): 263 | "" 264 | doc1 = vd.read_json(self.link_doc_path) 265 | predicates = ["sample-predicate-2", 266 | "sample-predicate-1" 267 | ] 268 | combined1 = ["sample-relation-3"] 269 | combined2 = ["sample-relation-5"] 270 | check1 = vd.validate_entity_predicate_link_content(doc1, 271 | combined1, 272 | predicates) 273 | check2 = vd.validate_entity_predicate_link_content(doc1, 274 | combined2, 275 | predicates) 276 | self.assertEqual(True, check1[0]) 277 | self.assertEqual(False, check2[0]) 278 | 279 | 280 | if __name__ == "__main__": 281 | unittest.main() 282 | -------------------------------------------------------------------------------- /suite/validator.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: validate asset documents 4 | 5 | import os 6 | import json 7 | import glob 8 | import sys 9 | 10 | 11 | def read_json(jsonpath: str) -> dict: 12 | "read json object from given path" 13 | assert os.path.isfile(jsonpath) 14 | with open(jsonpath, "r", encoding="utf-8") as fd: 15 | myfile = json.load(fd) 16 | return myfile 17 | 18 | 19 | def get_keys_array_from_object(obj: dict) -> dict: 20 | "get keys that are associated to objects from object" 21 | array_container = {} 22 | for key, value in obj.items(): 23 | if isinstance(value, dict): 24 | array_container[key] = value 25 | return array_container 26 | 27 | 28 | def check_key_value_string(key: str, value: str): 29 | "validate key and value for string" 30 | if not isinstance(key, str): 31 | return False 32 | if not isinstance(value, str): 33 | return False 34 | if key.isdigit(): 35 | return False 36 | return True 37 | 38 | 39 | def check_key_value_string_object(obj: dict): 40 | """ 41 | apply check key value string to object 42 | 43 | assumed structure 44 | {"my-key-string": "my value string"} 45 | """ 46 | if not isinstance(obj, dict): 47 | return False 48 | for key, value in obj.items(): 49 | if not check_key_value_string(key, value): 50 | return False 51 | return True 52 | 53 | 54 | def check_key_value_int(key: str, value: str): 55 | "validate key and value for integer keys" 56 | if not isinstance(key, str): 57 | return False 58 | if not isinstance(value, str): 59 | return False 60 | if not key.isdigit(): 61 | return False 62 | return True 63 | 64 | 65 | def check_key_value_int_object(obj: dict): 66 | """ 67 | apply check key value to int object 68 | 69 | assumed structure 70 | {"0": "my value string", "1": "my other string"} 71 | """ 72 | if not isinstance(obj, dict): 73 | return False 74 | for key, value in obj.items(): 75 | if not check_key_value_int(key, value): 76 | return False 77 | return True 78 | 79 | 80 | def check_key_key_value_int_object(key: str, obj: dict) -> bool: 81 | """ 82 | check key and value int object 83 | 84 | assumed structure 85 | {"my-string-key": {"0": "my value string", "1": "my other string"}} 86 | """ 87 | if not isinstance(key, str): 88 | return False 89 | if not check_key_value_int_object(obj): 90 | return False 91 | return True 92 | 93 | 94 | def check_key_key_value_string_object(key: str, obj: dict) -> bool: 95 | """ 96 | check key and value string object 97 | 98 | assumed structure 99 | {"my-string-key": {"my-string-key-1": "my value string", 100 | "my-string-key-2": "my other string"}} 101 | """ 102 | if not isinstance(key, str): 103 | return False 104 | if not check_key_value_string_object(obj): 105 | return False 106 | return True 107 | 108 | 109 | def validate_simple_authority_structure(author_file: dict) -> bool: 110 | """ 111 | given a simple authority file output whether it is valid or not 112 | 113 | Simple Authority Spec 114 | ---------------------- 115 | 116 | {"simple-no-1": {"some-value": "value definition"}, 117 | "simple-no-n": {"some-value": "value definition"} 118 | } 119 | """ 120 | assumed_structure = """ 121 | {"simple-sample-word-1": {"lorem": ""}, 122 | "simple-no-n": {"some-value": "value definition"} 123 | } 124 | """ 125 | keys = set() 126 | for author_key, author_value in author_file.items(): 127 | message = "Key value string object failed: {0}, {1}" 128 | message = message.format(author_key, str(author_value)) 129 | if not check_key_key_value_string_object(author_key, author_value): 130 | return False, author_key, author_value, assumed_structure, message 131 | value_length = len(author_value) 132 | if value_length > 1: 133 | message = "author value must not have more than one key" 134 | message += " value pairs" 135 | return False, author_key, author_value, assumed_structure, message 136 | if author_key not in keys: 137 | keys.add(author_key) 138 | else: 139 | message = "Key: " + author_key + " exists in document" 140 | return False, author_key, author_value, assumed_structure, message 141 | return [True] 142 | 143 | 144 | def validate_combined_authority_structure(author_file: dict) -> bool: 145 | """ 146 | given a combined authority file output whether it is valid or not 147 | 148 | Combined Authority Spec 149 | ---------------------- 150 | 151 | { 152 | "sample-combined-grammar-1": { 153 | "coordinating conjunction": "", 154 | "sample-relation-2": {"0": "sample-grammar-1"} 155 | }, 156 | "sample-combined-grammar-2": { 157 | "feminine substantif": "", 158 | "sample-relation-2": { 159 | "0": "sample-grammar-6", "1": "sample-grammar-2" 160 | } 161 | } 162 | } 163 | """ 164 | keys = set() 165 | assumed_structure = """ 166 | { 167 | "sample-combined-grammar-1": { 168 | "coordinating conjunction": "", 169 | "sample-relation-2": {"0": "sample-grammar-1"} 170 | }, 171 | "sample-combined-grammar-2": { 172 | "feminine substantif": "", 173 | "sample-relation-2": { 174 | "0": "sample-grammar-6", "1": "sample-grammar-2" 175 | } 176 | } 177 | } 178 | """ 179 | for author_key, author_value in author_file.items(): 180 | value_length = len(author_value) 181 | if value_length > 2: 182 | message = "author value must not have more than two keys" 183 | message += " value pairs" 184 | return False, author_key, author_value, assumed_structure, message 185 | if author_key not in keys: 186 | keys.add(author_key) 187 | else: 188 | message = "Key: " + author_key + " exists in document" 189 | return False, author_key, author_value, assumed_structure, message 190 | # 191 | for author_value_key, author_value_value in author_value.items(): 192 | first_check = check_key_value_string(author_value_key, 193 | author_value_value) 194 | second_check = check_key_key_value_int_object(author_value_key, 195 | author_value_value) 196 | if not (first_check or second_check): 197 | # 198 | message = "Author value items: key: " 199 | message += str(author_value_key) 200 | message += ", value: " + str(author_value_value) 201 | message += "\nValue string check: " + str(first_check) 202 | message += "\nValue int object check: " + str(second_check) 203 | return (False, author_key, 204 | author_value, assumed_structure, message) 205 | return [True] 206 | 207 | 208 | def validate_entity_predicate_structure(predicate_file: dict) -> bool: 209 | """ 210 | validate predicate, entity, and entity predicate link file structure 211 | 212 | assumed structure 213 | 214 | { "entity/predicate-1": {"another-simple-id-no-0": {"0": "simple-id-no-1"}}, 215 | "entity/predicate-2": { 216 | "another-simple/combined-id-no-0": { 217 | "0": "simple-id-no-2" 218 | } 219 | } 220 | } 221 | 222 | 223 | """ 224 | assumed_structure = """ 225 | { "entity/predicate-1": { 226 | "another-simple/combined-id-no-0": { 227 | 0: "simple/combined-id-no-1" 228 | } 229 | }, 230 | "entity/predicate-2": { 231 | "another-simple/combined-id-no-0": { 232 | 0: "simple/combined-id-no-2" 233 | } 234 | } 235 | } 236 | """ 237 | keys = set() 238 | for predicate_key, predicate_value in predicate_file.items(): 239 | if not isinstance(predicate_key, str): 240 | return False, predicate_key, predicate_value, assumed_structure 241 | for author_key, author_value_array in predicate_value.items(): 242 | if not check_key_key_value_int_object(author_key, 243 | author_value_array): 244 | return False, predicate_key, predicate_value, assumed_structure 245 | if predicate_key not in keys: 246 | keys.add(predicate_key) 247 | else: 248 | return False, predicate_key, predicate_value, assumed_structure 249 | return [True] 250 | 251 | 252 | def check_structure_proc(checkfn: lambda x: x, filetype: str, params: dict): 253 | "Check structure for a given file using check function" 254 | check = checkfn(**params) 255 | if check[0] is False: 256 | mess = filetype + " file is not valid." 257 | mess += " Here is the key value pair that is problematic: " 258 | mess += ",".join([str(check[1]), str(check[2])]) 259 | mess += ". See also the assumed file structure: " + check[3] 260 | raise ValueError(mess) 261 | 262 | 263 | def check_simple_authority_structure(author_file: dict) -> None: 264 | "check simple authority file" 265 | check_structure_proc(params={"author_file": author_file}, 266 | checkfn=validate_simple_authority_structure, 267 | filetype="Simple Authority") 268 | 269 | 270 | def check_combined_authority_structure(author_file: dict) -> None: 271 | "check combined authority file" 272 | check_structure_proc(params={"author_file": author_file}, 273 | checkfn=validate_combined_authority_structure, 274 | filetype="Combined Authority") 275 | 276 | 277 | def check_predicate_file_structure(predicate_file: dict) -> None: 278 | "check predicate file structure" 279 | check_structure_proc(params={"predicate_file": predicate_file}, 280 | checkfn=validate_entity_predicate_structure, 281 | filetype="Predicate Document") 282 | 283 | 284 | def check_entity_file_structure(entity_file: dict) -> None: 285 | "check entity file structure" 286 | check_structure_proc(params={"predicate_file": entity_file}, 287 | checkfn=validate_entity_predicate_structure, 288 | filetype="Entity Document") 289 | 290 | 291 | def check_entity_predicate_link_file_structure( 292 | entity_predicate_link_file: dict) -> None: 293 | "check entity predicate link file structure" 294 | check_structure_proc(params={"predicate_file": entity_predicate_link_file}, 295 | checkfn=validate_entity_predicate_structure, 296 | filetype="Entity Predicate Link Document") 297 | 298 | 299 | def validate_combined_authority_content(author_file: dict, 300 | simple_ids: list) -> bool: 301 | "validate combined authority files using simple ids" 302 | assumed_structure = """ 303 | { 304 | "combined-id-n": { 305 | "value": "value definition", 306 | "simple-id-no-1": { 307 | "0": "simple-no-0" 308 | } 309 | } 310 | } 311 | """ 312 | for author_key, author_value in author_file.items(): 313 | array_container = get_keys_array_from_object(author_value) 314 | for key, array_obj in array_container.items(): 315 | if key not in simple_ids: 316 | message = "Key: " + key + " not in id list." 317 | return False, author_key, author_value, message 318 | for value in array_obj.values(): 319 | if value not in simple_ids: 320 | message = "Value: " + value + " not in id list " 321 | return False, author_key, author_value, message 322 | return [True] 323 | 324 | 325 | def validate_entity_predicate_content(entity_predicate_file: dict, 326 | simple_combined_ids: list) -> list: 327 | "validate predicate file content" 328 | assumed_structure = """ 329 | { "entity/predicate-1": {"another-simple-id-no-0": {"0": "simple-id-no-1"}}, 330 | "entity/predicate-2": { 331 | "another-simple/combined-id-no-0": { 332 | "0": "simple-id-no-2" 333 | } 334 | }, 335 | "entity/predicate-3": { 336 | "another-simple/combined-id-no-0": { 337 | "0": "entity/predicate-id-no-2" 338 | } 339 | } 340 | } 341 | """ 342 | keys = list(entity_predicate_file.keys()) 343 | for predicate_key, predicate_value in entity_predicate_file.items(): 344 | for key, array_obj in predicate_value.items(): 345 | if key not in simple_combined_ids: 346 | message = "Key: " + key + " not in id list." 347 | return False, predicate_key, predicate_value, message 348 | for value in array_obj.values(): 349 | first_check = value not in simple_combined_ids 350 | second_check = value not in keys 351 | cond = first_check and second_check 352 | if cond: 353 | message = "Value: " + value + "" 354 | message += "\nCondition: " + str(cond) 355 | message += "\nValue not in ids: " + str(first_check) 356 | message += "\nValue not in keys: " + str(second_check) 357 | return False, predicate_key, predicate_value, message 358 | return [True] 359 | 360 | 361 | def validate_entity_predicate_link_content(link_file: dict, 362 | simple_combined_ids: list, 363 | predicate_ids: list) -> list: 364 | "validate entity predicate link file content" 365 | for entity_id, link_values in link_file.items(): 366 | for authority_id, predicates in link_values.items(): 367 | if authority_id not in simple_combined_ids: 368 | message = "Authority id: " + authority_id + " not in id list." 369 | return False, entity_id, link_values, message 370 | for predicate in predicates.values(): 371 | if predicate not in predicate_ids: 372 | message = "Predicate value: " + predicate 373 | message += " not in available predicate ids" 374 | return False, entity_id, link_values, message 375 | return [True] 376 | 377 | 378 | def check_content_proc(checkfn: lambda x: x, 379 | filetype: str, params: dict): 380 | "Check content for a given file type" 381 | check = checkfn(**params) 382 | if check[0] is False: 383 | mess = filetype + " file is not valid." 384 | mess += "\nHere is the key value pair that is problematic: " 385 | mess += ",".join([str(check[1]), str(check[2])]) 386 | mess += ".\nSee also the assumed file structure: " + check[3] 387 | mess += "\nSee the message of validation function: " + check[4] 388 | raise ValueError(mess) 389 | -------------------------------------------------------------------------------- /suite/io/iprimitive.py: -------------------------------------------------------------------------------- 1 | # author: Kaan Eraslan 2 | # license: see, LICENSE 3 | # purpose: io for primitives in multiple formats 4 | 5 | from suite.dtype.primitive import ConstraintString 6 | from suite.dtype.primitive import ConstantString 7 | from suite.dtype.primitive import NonNumericString 8 | from suite.dtype.primitive import PrimitiveMaker 9 | 10 | from lxml import etree 11 | import json 12 | import yaml 13 | import pickle 14 | import dill 15 | 16 | 17 | class _PrimitiveIo: 18 | "Base class for all primitive io" 19 | 20 | def __init__(self, primitive, primitiveType, classNameSep="-"): 21 | if not isinstance(primitive, primitiveType): 22 | raise TypeError( 23 | "Given primitive type is: " 24 | + primitive.__class_.__name__ 25 | + " it must be: " 26 | + primitiveType.__name__ 27 | ) 28 | self.primitive = primitive 29 | self.primitiveType = primitiveType 30 | 31 | @classmethod 32 | def check_value_error(cls, objval: str, wantedVal: str, messPrefix: str): 33 | if objval != wantedVal: 34 | raise ValueError(messPrefix + objval + " it must be: " + wantedVal) 35 | return 36 | 37 | 38 | class _PrimitiveXmlIo(_PrimitiveIo): 39 | "io primitive in xml format" 40 | 41 | def __init__(self, primitive, primitiveType): 42 | super().__init__(primitive, primitiveType) 43 | 44 | def to_element(self) -> etree.Element: 45 | raise NotImplementedError 46 | 47 | @classmethod 48 | def from_element(self, el: etree.Element): 49 | raise NotImplementedError 50 | 51 | def __str__(self): 52 | return "Xml Io for primitive: " + str(self.primitive) 53 | 54 | 55 | class _PrimitiveJsonIo(_PrimitiveIo): 56 | "Io Primitive in json format" 57 | 58 | def __init__(self, primitive, primitiveType): 59 | super().__init__(primitive, primitiveType) 60 | 61 | def to_dict(self): 62 | raise NotImplementedError 63 | 64 | def to_json(self): 65 | objdict = self.to_dict() 66 | return json.dumps(objdict, ensure_ascii=False, indent=2, sort_keys=True) 67 | 68 | @classmethod 69 | def from_json(self, jsonstr: str): 70 | raise NotImplementedError 71 | 72 | def toJSON(self): 73 | return self.to_json() 74 | 75 | def __str__(self): 76 | return "Json Renderer for primitive: " + str(self.primitive) 77 | 78 | 79 | class _PrimitiveIoBuilder: 80 | "Base Io builder for available primitive io objects" 81 | SUPPORTED = ["xml", "json"] 82 | 83 | class XmlIo: 84 | pass 85 | 86 | class JsonIo: 87 | pass 88 | 89 | @classmethod 90 | def getIoClass(cls, render_format: str): 91 | render_format = render_format.lower() 92 | if render_format == cls.SUPPORTED[0]: 93 | return cls.XmlIo 94 | elif render_format == cls.SUPPORTED[1]: 95 | return cls.JsonIo 96 | else: 97 | raise ValueError( 98 | render_format + " not in supported formats: " + ",".join(cls.SUPPORTED) 99 | ) 100 | 101 | 102 | class ConstantStringIo(_PrimitiveIoBuilder): 103 | "Io builder for constant string primitive" 104 | 105 | def __init__(self, mystr: ConstantString): 106 | super().__init__() 107 | if not mystr.isValid(): 108 | raise ValueError("Given ConstantString: " + str(mystr) + " is not valid") 109 | self.constr = mystr 110 | 111 | class XmlIo(_PrimitiveXmlIo): 112 | def __init__(self, mystr: ConstantString): 113 | super().__init__(mystr, ConstantString) 114 | 115 | def to_element(self) -> etree.Element: 116 | "io default representation for constant string" 117 | root = etree.Element("primitive") 118 | root.text = str(self.primitive) 119 | root.set("class", self.primitiveType.__name__) 120 | return root 121 | 122 | def to_dict(self): 123 | "render as a dict" 124 | pdict = {} 125 | pdict["class"] = self.primitiveType.__name__ 126 | pdict["type"] = "primitive" 127 | pdict["value"] = str(self.primitive) 128 | return pdict 129 | 130 | @classmethod 131 | def from_dict(cls, cdict: dict) -> ConstantString: 132 | "render constant string from dict" 133 | objtype = cdict.get("type", "") 134 | objclass = cdict.get("class", "") 135 | cls.check_value_error( 136 | objtype, "primitive", messPrefix="Given object type: " 137 | ) 138 | cls.check_value_error( 139 | objclass, "ConstantString", messPrefix="Given object class: " 140 | ) 141 | maker = PrimitiveMaker("constant string") 142 | constr = maker.make(mystr=cdict["value"]) 143 | return constr 144 | 145 | @classmethod 146 | def from_element(cls, el: etree.Element) -> ConstantString: 147 | "from element" 148 | elclass = el.get("class") 149 | cls.check_value_error( 150 | objval=elclass, 151 | wantedVal="ConstantString", 152 | messPrefix="Given tag class: ", 153 | ) 154 | elstr = el.text 155 | maker = PrimitiveMaker("constant string") 156 | constr = maker.make(mystr=elstr) 157 | return constr 158 | 159 | class JsonIo(_PrimitiveJsonIo): 160 | def __init__(self, mystr: ConstantString): 161 | super().__init__(mystr, ConstantString) 162 | 163 | def to_dict(self): 164 | "render as a dict" 165 | pdict = {} 166 | pdict["class"] = self.primitiveType.__name__ 167 | pdict["type"] = "primitive" 168 | pdict["value"] = str(self.primitive) 169 | return pdict 170 | 171 | @classmethod 172 | def from_dict(cls, cdict: dict) -> ConstantString: 173 | "render constant string from dict" 174 | objtype = cdict.get("type", "") 175 | objclass = cdict.get("class", "") 176 | cls.check_value_error( 177 | objtype, "primitive", messPrefix="Given object type: " 178 | ) 179 | cls.check_value_error( 180 | objclass, "ConstantString", messPrefix="Given object class: " 181 | ) 182 | maker = PrimitiveMaker("constant string") 183 | constr = maker.make(mystr=cdict["value"]) 184 | return constr 185 | 186 | @classmethod 187 | def from_json(cls, jsonstr: str) -> ConstantString: 188 | "obtain from constant string" 189 | obdict = json.loads(jsonstr) 190 | return cls.from_dict(obdict) 191 | 192 | def getIoInstance(self, render_format: str): 193 | "render constraint string in given format" 194 | return self.getIoClass(render_format)(self.constr) 195 | 196 | def __repr__(self): 197 | return "Io builder for: " + repr(self.constr) 198 | 199 | 200 | class ConstraintStringIo(_PrimitiveIoBuilder): 201 | "Io for a constraint string in given format" 202 | SUPPORTED = ["xml", "json"] 203 | 204 | def __init__(self, cstr: ConstraintString): 205 | if not cstr.isValid(): 206 | raise ValueError("Constraint string: " + str(cstr) + "is not valid") 207 | self.cstr = cstr 208 | 209 | class XmlIo(_PrimitiveXmlIo): 210 | "Io Constraint String as Xml" 211 | 212 | def __init__(self, cstr: ConstraintString): 213 | super().__init__(cstr, ConstraintString) 214 | 215 | @staticmethod 216 | def text_to_constant_string(text: str) -> ConstantString: 217 | "transform string to constant string" 218 | maker = PrimitiveMaker("constant string") 219 | return maker.make(mystr=text) 220 | 221 | def to_element(self): 222 | "render default representation for constraint string" 223 | root = etree.Element("primitive") 224 | root.text = str(self.primitive) 225 | root.set("class", self.primitiveType.__name__) 226 | myfn = dill.dumps(self.primitive.fn) 227 | root.set("constraint", myfn.hex()) 228 | return root 229 | 230 | def to_dict(self): 231 | "Default representation in python dict" 232 | pdict = {} 233 | pdict["class"] = self.primitiveType.__name__ 234 | pdict["constraint"] = dill.dumps(self.primitive.fn).hex() 235 | pdict["type"] = "primitive" 236 | pdict["value"] = self.constant_string_to_dict(self.primitive) 237 | return pdict 238 | 239 | @classmethod 240 | def from_element(cls, element: etree.Element): 241 | "" 242 | elclass = element.get("class") 243 | eltag = element.tag 244 | cls.check_value_error(elclass, "ConstraintString", "Given element class: ") 245 | cls.check_value_error(eltag, "primitive", "Given element tag: ") 246 | 247 | myfn = bytes.fromhex(element.get("constraint")) 248 | myfn = dill.loads(myfn) 249 | cstr = element.text 250 | constr = cls.text_to_constant_string(cstr) 251 | maker = PrimitiveMaker("constraint string") 252 | return maker.make(mystr=constr, fnc=myfn) 253 | 254 | @classmethod 255 | def from_dict(cls, cdict: dict) -> ConstraintString: 256 | "transform dict to ConstraintString" 257 | objclass = cdict.get("class", "") 258 | objtype = cdict.get("type", "") 259 | cls.check_value_error(objtype, "primitive", "Given object type: ") 260 | cls.check_value_error(objclass, "ConstraintString", "Given object class: ") 261 | myfn = bytes.fromhex(cdict["constraint"]) 262 | myfnc = dill.loads(myfn) 263 | constrdict = cdict["value"] 264 | constr = cls.dict_to_constant_string(constrdict) 265 | maker = PrimitiveMaker("constraint string") 266 | return maker.make(mystr=constr, fnc=myfnc) 267 | 268 | class JsonIo(_PrimitiveJsonIo): 269 | "Render Constraint String as Json" 270 | 271 | def __init__(self, cstr: ConstraintString): 272 | super().__init__(cstr, ConstraintString) 273 | 274 | @classmethod 275 | def constant_string_to_dict(cls, constring: ConstraintString): 276 | "transform constant string to dict" 277 | constr = constring.cstr 278 | consio = ConstantStringIo(constr) 279 | ionst = consio.getIoInstance("json") 280 | jstr = ionst.to_json() 281 | return json.loads(jstr) 282 | 283 | def to_dict(self): 284 | "Default representation in python dict" 285 | pdict = {} 286 | pdict["class"] = self.primitiveType.__name__ 287 | pdict["constraint"] = dill.dumps(self.primitive.fn).hex() 288 | pdict["type"] = "primitive" 289 | pdict["value"] = self.constant_string_to_dict(self.primitive) 290 | return pdict 291 | 292 | @classmethod 293 | def dict_to_constant_string(cls, consdict: dict): 294 | "transform dict representation to constant string" 295 | consio = ConstantStringIo 296 | iocls = consio.getIoClass("json") 297 | constr = iocls.from_dict(consdict) 298 | return constr 299 | 300 | @classmethod 301 | def from_dict(cls, cdict: dict) -> ConstraintString: 302 | "transform dict to ConstraintString" 303 | objclass = cdict.get("class", "") 304 | objtype = cdict.get("type", "") 305 | cls.check_value_error(objtype, "primitive", "Given object type: ") 306 | cls.check_value_error(objclass, "ConstraintString", "Given object class: ") 307 | myfn = bytes.fromhex(cdict["constraint"]) 308 | myfnc = dill.loads(myfn) 309 | constrdict = cdict["value"] 310 | constr = cls.dict_to_constant_string(constrdict) 311 | maker = PrimitiveMaker("constraint string") 312 | return maker.make(mystr=constr, fnc=myfnc) 313 | 314 | @classmethod 315 | def from_json(cls, jsonstr: str): 316 | "construct object from json" 317 | objdict = json.loads(jsonstr) 318 | return cls.from_dict(objdict) 319 | 320 | def getIoInstance(self, render_format: str): 321 | "render constraint string in given format" 322 | return self.getIoClass(render_format)(self.cstr) 323 | 324 | def __repr__(self): 325 | return "Io builder for: " + repr(self.cstr) 326 | 327 | 328 | class NonNumericStringIo(_PrimitiveIoBuilder): 329 | "Render a non numeric in given format" 330 | SUPPORTED = ["xml", "json"] 331 | 332 | def __init__(self, nnstr: NonNumericString): 333 | if not nnstr.isValid(): 334 | raise ValueError("Given NonNumericString: " + str(nnstr) + " is not valid") 335 | assert nnstr.isValid() 336 | self.nnstr = nnstr 337 | 338 | class XmlIo(_PrimitiveXmlIo): 339 | "Io Constraint String as Xml" 340 | 341 | def __init__(self, cstr: NonNumericString): 342 | super().__init__(cstr, NonNumericString) 343 | 344 | @staticmethod 345 | def text_to_constant_string(text: str) -> ConstantString: 346 | "transform string to constant string" 347 | maker = PrimitiveMaker("constant string") 348 | return maker.make(mystr=text) 349 | 350 | def to_element(self): 351 | "render default representation for constraint string" 352 | root = etree.Element("primitive") 353 | root.text = str(self.primitive) 354 | root.set("class", self.primitiveType.__name__) 355 | myfn = dill.dumps(self.primitive.fn) 356 | root.set("constraint", myfn.hex()) 357 | return root 358 | 359 | def to_dict(self): 360 | "Default representation in python dict" 361 | pdict = {} 362 | pdict["class"] = self.primitiveType.__name__ 363 | pdict["constraint"] = dill.dumps(self.primitive.fn).hex() 364 | pdict["type"] = "primitive" 365 | pdict["value"] = self.constant_string_to_dict(self.primitive) 366 | return pdict 367 | 368 | @classmethod 369 | def from_dict(cls, cdict: dict): 370 | "make object from given dict" 371 | objclass = cdict.get("class", "") 372 | objtype = cdict.get("type", "") 373 | cls.check_value_error(objclass, "NonNumericString", "Given object class: ") 374 | cls.check_value_error(objtype, "primitive", "Given object type: ") 375 | 376 | constr = cls.dict_to_constant_string(cdict["value"]) 377 | maker = PrimitiveMaker("non numeric string") 378 | return maker.make(mystr=constr) 379 | 380 | @classmethod 381 | def from_element(cls, element: etree.Element): 382 | "" 383 | elclass = element.get("class") 384 | eltag = element.tag 385 | cls.check_value_error(elclass, "NonNumericString", "Given element class: ") 386 | cls.check_value_error(eltag, "primitive", "Given element tag: ") 387 | cstr = element.text 388 | constr = cls.text_to_constant_string(cstr) 389 | maker = PrimitiveMaker("non numeric string") 390 | return maker.make(mystr=constr) 391 | 392 | class JsonIo(_PrimitiveJsonIo): 393 | "Io Constraint String as Json" 394 | 395 | def __init__(self, cstr: NonNumericString): 396 | super().__init__(cstr, NonNumericString) 397 | 398 | @classmethod 399 | def constant_string_to_dict(cls, conststr: NonNumericString): 400 | "transform constant string of a constraint string to dict" 401 | constr = conststr.cstr 402 | consio = ConstantStringIo(constr) 403 | ionst = consio.getIoInstance("json") 404 | jstr = ionst.to_json() 405 | return json.loads(jstr) 406 | 407 | def to_dict(self): 408 | "Default representation in python dict" 409 | pdict = {} 410 | pdict["class"] = self.primitiveType.__name__ 411 | pdict["constraint"] = dill.dumps(self.primitive.fn).hex() 412 | pdict["type"] = "primitive" 413 | pdict["value"] = self.constant_string_to_dict(self.primitive) 414 | return pdict 415 | 416 | @classmethod 417 | def dict_to_constant_string(cls, consdict: dict): 418 | "transform dict representation to constant string" 419 | consio = ConstantStringIo 420 | iocls = consio.getIoClass("json") 421 | constr = iocls.from_dict(consdict) 422 | if not constr.isValid(): 423 | raise ValueError("ConstantString: " + str(constr) + " is not valid") 424 | return constr 425 | 426 | @classmethod 427 | def from_json(cls, jsonstr: str): 428 | "construct object from json" 429 | objdict = json.loads(jsonstr) 430 | return cls.from_dict(objdict) 431 | 432 | @classmethod 433 | def from_dict(cls, cdict: dict): 434 | "make object from given dict" 435 | objclass = cdict.get("class", "") 436 | objtype = cdict.get("type", "") 437 | cls.check_value_error(objclass, "NonNumericString", "Given object class: ") 438 | cls.check_value_error(objtype, "primitive", "Given object type: ") 439 | 440 | constr = cls.dict_to_constant_string(cdict["value"]) 441 | maker = PrimitiveMaker("non numeric string") 442 | return maker.make(mystr=constr) 443 | 444 | def getIoInstance(self, render_format: str): 445 | "render constraint string in given format" 446 | return self.getIoClass(render_format)(self.nnstr) 447 | 448 | def __repr__(self): 449 | return "Io builder for " + repr(self.cstr) 450 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Attribution 4.0 International 3 | 4 | ======================================================================= 5 | 6 | Creative Commons Corporation ("Creative Commons") is not a law firm and 7 | does not provide legal services or legal advice. Distribution of 8 | Creative Commons public licenses does not create a lawyer-client or 9 | other relationship. Creative Commons makes its licenses and related 10 | information available on an "as-is" basis. Creative Commons gives no 11 | warranties regarding its licenses, any material licensed under their 12 | terms and conditions, or any related information. Creative Commons 13 | disclaims all liability for damages resulting from their use to the 14 | fullest extent possible. 15 | 16 | Using Creative Commons Public Licenses 17 | 18 | Creative Commons public licenses provide a standard set of terms and 19 | conditions that creators and other rights holders may use to share 20 | original works of authorship and other material subject to copyright 21 | and certain other rights specified in the public license below. The 22 | following considerations are for informational purposes only, are not 23 | exhaustive, and do not form part of our licenses. 24 | 25 | Considerations for licensors: Our public licenses are 26 | intended for use by those authorized to give the public 27 | permission to use material in ways otherwise restricted by 28 | copyright and certain other rights. Our licenses are 29 | irrevocable. Licensors should read and understand the terms 30 | and conditions of the license they choose before applying it. 31 | Licensors should also secure all rights necessary before 32 | applying our licenses so that the public can reuse the 33 | material as expected. Licensors should clearly mark any 34 | material not subject to the license. This includes other CC- 35 | licensed material, or material used under an exception or 36 | limitation to copyright. More considerations for licensors: 37 | wiki.creativecommons.org/Considerations_for_licensors 38 | 39 | Considerations for the public: By using one of our public 40 | licenses, a licensor grants the public permission to use the 41 | licensed material under specified terms and conditions. If 42 | the licensor's permission is not necessary for any reason--for 43 | example, because of any applicable exception or limitation to 44 | copyright--then that use is not regulated by the license. Our 45 | licenses grant only permissions under copyright and certain 46 | other rights that a licensor has authority to grant. Use of 47 | the licensed material may still be restricted for other 48 | reasons, including because others have copyright or other 49 | rights in the material. A licensor may make special requests, 50 | such as asking that all changes be marked or described. 51 | Although not required by our licenses, you are encouraged to 52 | respect those requests where reasonable. More_considerations 53 | for the public: 54 | wiki.creativecommons.org/Considerations_for_licensees 55 | 56 | ======================================================================= 57 | 58 | Creative Commons Attribution 4.0 International Public License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution 4.0 International Public License ("Public License"). To the 63 | extent this Public License may be interpreted as a contract, You are 64 | granted the Licensed Rights in consideration of Your acceptance of 65 | these terms and conditions, and the Licensor grants You such rights in 66 | consideration of benefits the Licensor receives from making the 67 | Licensed Material available under these terms and conditions. 68 | 69 | 70 | Section 1 -- Definitions. 71 | 72 | a. Adapted Material means material subject to Copyright and Similar 73 | Rights that is derived from or based upon the Licensed Material 74 | and in which the Licensed Material is translated, altered, 75 | arranged, transformed, or otherwise modified in a manner requiring 76 | permission under the Copyright and Similar Rights held by the 77 | Licensor. For purposes of this Public License, where the Licensed 78 | Material is a musical work, performance, or sound recording, 79 | Adapted Material is always produced where the Licensed Material is 80 | synched in timed relation with a moving image. 81 | 82 | b. Adapter's License means the license You apply to Your Copyright 83 | and Similar Rights in Your contributions to Adapted Material in 84 | accordance with the terms and conditions of this Public License. 85 | 86 | c. Copyright and Similar Rights means copyright and/or similar rights 87 | closely related to copyright including, without limitation, 88 | performance, broadcast, sound recording, and Sui Generis Database 89 | Rights, without regard to how the rights are labeled or 90 | categorized. For purposes of this Public License, the rights 91 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 92 | Rights. 93 | 94 | d. Effective Technological Measures means those measures that, in the 95 | absence of proper authority, may not be circumvented under laws 96 | fulfilling obligations under Article 11 of the WIPO Copyright 97 | Treaty adopted on December 20, 1996, and/or similar international 98 | agreements. 99 | 100 | e. Exceptions and Limitations means fair use, fair dealing, and/or 101 | any other exception or limitation to Copyright and Similar Rights 102 | that applies to Your use of the Licensed Material. 103 | 104 | f. Licensed Material means the artistic or literary work, database, 105 | or other material to which the Licensor applied this Public 106 | License. 107 | 108 | g. Licensed Rights means the rights granted to You subject to the 109 | terms and conditions of this Public License, which are limited to 110 | all Copyright and Similar Rights that apply to Your use of the 111 | Licensed Material and that the Licensor has authority to license. 112 | 113 | h. Licensor means the individual(s) or entity(ies) granting rights 114 | under this Public License. 115 | 116 | i. Share means to provide material to the public by any means or 117 | process that requires permission under the Licensed Rights, such 118 | as reproduction, public display, public performance, distribution, 119 | dissemination, communication, or importation, and to make material 120 | available to the public including in ways that members of the 121 | public may access the material from a place and at a time 122 | individually chosen by them. 123 | 124 | j. Sui Generis Database Rights means rights other than copyright 125 | resulting from Directive 96/9/EC of the European Parliament and of 126 | the Council of 11 March 1996 on the legal protection of databases, 127 | as amended and/or succeeded, as well as other essentially 128 | equivalent rights anywhere in the world. 129 | 130 | k. You means the individual or entity exercising the Licensed Rights 131 | under this Public License. Your has a corresponding meaning. 132 | 133 | 134 | Section 2 -- Scope. 135 | 136 | a. License grant. 137 | 138 | 1. Subject to the terms and conditions of this Public License, 139 | the Licensor hereby grants You a worldwide, royalty-free, 140 | non-sublicensable, non-exclusive, irrevocable license to 141 | exercise the Licensed Rights in the Licensed Material to: 142 | 143 | a. reproduce and Share the Licensed Material, in whole or 144 | in part; and 145 | 146 | b. produce, reproduce, and Share Adapted Material. 147 | 148 | 2. Exceptions and Limitations. For the avoidance of doubt, where 149 | Exceptions and Limitations apply to Your use, this Public 150 | License does not apply, and You do not need to comply with 151 | its terms and conditions. 152 | 153 | 3. Term. The term of this Public License is specified in Section 154 | 6(a). 155 | 156 | 4. Media and formats; technical modifications allowed. The 157 | Licensor authorizes You to exercise the Licensed Rights in 158 | all media and formats whether now known or hereafter created, 159 | and to make technical modifications necessary to do so. The 160 | Licensor waives and/or agrees not to assert any right or 161 | authority to forbid You from making technical modifications 162 | necessary to exercise the Licensed Rights, including 163 | technical modifications necessary to circumvent Effective 164 | Technological Measures. For purposes of this Public License, 165 | simply making modifications authorized by this Section 2(a) 166 | (4) never produces Adapted Material. 167 | 168 | 5. Downstream recipients. 169 | 170 | a. Offer from the Licensor -- Licensed Material. Every 171 | recipient of the Licensed Material automatically 172 | receives an offer from the Licensor to exercise the 173 | Licensed Rights under the terms and conditions of this 174 | Public License. 175 | 176 | b. No downstream restrictions. You may not offer or impose 177 | any additional or different terms or conditions on, or 178 | apply any Effective Technological Measures to, the 179 | Licensed Material if doing so restricts exercise of the 180 | Licensed Rights by any recipient of the Licensed 181 | Material. 182 | 183 | 6. No endorsement. Nothing in this Public License constitutes or 184 | may be construed as permission to assert or imply that You 185 | are, or that Your use of the Licensed Material is, connected 186 | with, or sponsored, endorsed, or granted official status by, 187 | the Licensor or others designated to receive attribution as 188 | provided in Section 3(a)(1)(A)(i). 189 | 190 | b. Other rights. 191 | 192 | 1. Moral rights, such as the right of integrity, are not 193 | licensed under this Public License, nor are publicity, 194 | privacy, and/or other similar personality rights; however, to 195 | the extent possible, the Licensor waives and/or agrees not to 196 | assert any such rights held by the Licensor to the limited 197 | extent necessary to allow You to exercise the Licensed 198 | Rights, but not otherwise. 199 | 200 | 2. Patent and trademark rights are not licensed under this 201 | Public License. 202 | 203 | 3. To the extent possible, the Licensor waives any right to 204 | collect royalties from You for the exercise of the Licensed 205 | Rights, whether directly or through a collecting society 206 | under any voluntary or waivable statutory or compulsory 207 | licensing scheme. In all other cases the Licensor expressly 208 | reserves any right to collect such royalties. 209 | 210 | 211 | Section 3 -- License Conditions. 212 | 213 | Your exercise of the Licensed Rights is expressly made subject to the 214 | following conditions. 215 | 216 | a. Attribution. 217 | 218 | 1. If You Share the Licensed Material (including in modified 219 | form), You must: 220 | 221 | a. retain the following if it is supplied by the Licensor 222 | with the Licensed Material: 223 | 224 | i. identification of the creator(s) of the Licensed 225 | Material and any others designated to receive 226 | attribution, in any reasonable manner requested by 227 | the Licensor (including by pseudonym if 228 | designated); 229 | 230 | ii. a copyright notice; 231 | 232 | iii. a notice that refers to this Public License; 233 | 234 | iv. a notice that refers to the disclaimer of 235 | warranties; 236 | 237 | v. a URI or hyperlink to the Licensed Material to the 238 | extent reasonably practicable; 239 | 240 | b. indicate if You modified the Licensed Material and 241 | retain an indication of any previous modifications; and 242 | 243 | c. indicate the Licensed Material is licensed under this 244 | Public License, and include the text of, or the URI or 245 | hyperlink to, this Public License. 246 | 247 | 2. You may satisfy the conditions in Section 3(a)(1) in any 248 | reasonable manner based on the medium, means, and context in 249 | which You Share the Licensed Material. For example, it may be 250 | reasonable to satisfy the conditions by providing a URI or 251 | hyperlink to a resource that includes the required 252 | information. 253 | 254 | 3. If requested by the Licensor, You must remove any of the 255 | information required by Section 3(a)(1)(A) to the extent 256 | reasonably practicable. 257 | 258 | 4. If You Share Adapted Material You produce, the Adapter's 259 | License You apply must not prevent recipients of the Adapted 260 | Material from complying with this Public License. 261 | 262 | 263 | Section 4 -- Sui Generis Database Rights. 264 | 265 | Where the Licensed Rights include Sui Generis Database Rights that 266 | apply to Your use of the Licensed Material: 267 | 268 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 269 | to extract, reuse, reproduce, and Share all or a substantial 270 | portion of the contents of the database; 271 | 272 | b. if You include all or a substantial portion of the database 273 | contents in a database in which You have Sui Generis Database 274 | Rights, then the database in which You have Sui Generis Database 275 | Rights (but not its individual contents) is Adapted Material; and 276 | 277 | c. You must comply with the conditions in Section 3(a) if You Share 278 | all or a substantial portion of the contents of the database. 279 | 280 | For the avoidance of doubt, this Section 4 supplements and does not 281 | replace Your obligations under this Public License where the Licensed 282 | Rights include other Copyright and Similar Rights. 283 | 284 | 285 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 286 | 287 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 288 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 289 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 290 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 291 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 292 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 293 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 294 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 295 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 296 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 297 | 298 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 299 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 300 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 301 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 302 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 303 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 304 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 305 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 306 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 307 | 308 | c. The disclaimer of warranties and limitation of liability provided 309 | above shall be interpreted in a manner that, to the extent 310 | possible, most closely approximates an absolute disclaimer and 311 | waiver of all liability. 312 | 313 | 314 | Section 6 -- Term and Termination. 315 | 316 | a. This Public License applies for the term of the Copyright and 317 | Similar Rights licensed here. However, if You fail to comply with 318 | this Public License, then Your rights under this Public License 319 | terminate automatically. 320 | 321 | b. Where Your right to use the Licensed Material has terminated under 322 | Section 6(a), it reinstates: 323 | 324 | 1. automatically as of the date the violation is cured, provided 325 | it is cured within 30 days of Your discovery of the 326 | violation; or 327 | 328 | 2. upon express reinstatement by the Licensor. 329 | 330 | For the avoidance of doubt, this Section 6(b) does not affect any 331 | right the Licensor may have to seek remedies for Your violations 332 | of this Public License. 333 | 334 | c. For the avoidance of doubt, the Licensor may also offer the 335 | Licensed Material under separate terms or conditions or stop 336 | distributing the Licensed Material at any time; however, doing so 337 | will not terminate this Public License. 338 | 339 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 340 | License. 341 | 342 | 343 | Section 7 -- Other Terms and Conditions. 344 | 345 | a. The Licensor shall not be bound by any additional or different 346 | terms or conditions communicated by You unless expressly agreed. 347 | 348 | b. Any arrangements, understandings, or agreements regarding the 349 | Licensed Material not stated herein are separate from and 350 | independent of the terms and conditions of this Public License. 351 | 352 | 353 | Section 8 -- Interpretation. 354 | 355 | a. For the avoidance of doubt, this Public License does not, and 356 | shall not be interpreted to, reduce, limit, restrict, or impose 357 | conditions on any use of the Licensed Material that could lawfully 358 | be made without permission under this Public License. 359 | 360 | b. To the extent possible, if any provision of this Public License is 361 | deemed unenforceable, it shall be automatically reformed to the 362 | minimum extent necessary to make it enforceable. If the provision 363 | cannot be reformed, it shall be severed from this Public License 364 | without affecting the enforceability of the remaining terms and 365 | conditions. 366 | 367 | c. No term or condition of this Public License will be waived and no 368 | failure to comply consented to unless expressly agreed to by the 369 | Licensor. 370 | 371 | d. Nothing in this Public License constitutes or may be interpreted 372 | as a limitation upon, or waiver of, any privileges and immunities 373 | that apply to the Licensor or You, including from the legal 374 | processes of any jurisdiction or authority. 375 | 376 | 377 | ======================================================================= 378 | 379 | Creative Commons is not a party to its public 380 | licenses. Notwithstanding, Creative Commons may elect to apply one of 381 | its public licenses to material it publishes and in those instances 382 | will be considered the “Licensor.” The text of the Creative Commons 383 | public licenses is dedicated to the public domain under the CC0 Public 384 | Domain Dedication. Except for the limited purpose of indicating that 385 | material is shared under a Creative Commons public license or as 386 | otherwise permitted by the Creative Commons policies published at 387 | creativecommons.org/policies, Creative Commons does not authorize the 388 | use of the trademark "Creative Commons" or any other trademark or logo 389 | of Creative Commons without its prior written consent including, 390 | without limitation, in connection with any unauthorized modifications 391 | to any of its public licenses or any other arrangements, 392 | understandings, or agreements concerning use of licensed material. For 393 | the avoidance of doubt, this paragraph does not form part of the 394 | public licenses. 395 | 396 | Creative Commons may be contacted at creativecommons.org. 397 | --------------------------------------------------------------------------------