diff --git a/Pipfile b/Pipfile index 78b00a2..3e5b3bb 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ faker = "*" streamlit-player = "*" [dev-packages] +mock-generators = {editable = true, path = "."} [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index 9380fbd..3eb7842 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9ea97856f4fe481f4177b7fba99320f4a4db7a871e6d685ad2e20e1b4929186f" + "sha256": "8541ff792fc307a32203cc04960e717db21fa70f46d6bb2d145becbb9aac37ef" }, "pipfile-spec": 6, "requires": { @@ -240,11 +240,11 @@ }, "faker": { "hashes": [ - "sha256:49060d40e6659e116f53353c5771ad2f2cbcd12b15771f49e3000a3a451f13ec", - "sha256:ac903ba8cb5adbce2cdd15e5536118d484bbe01126f3c774dd9f6df77b61232d" + "sha256:3aabe5b8c4ab50103a0c05c15e31a24b22045968d1ac0b7fb48d1c6244400495", + "sha256:6f882f5a3303c0690b3fb2dd3418839935a2dca155d38f1b2931b0079f3ce622" ], "index": "pypi", - "version": "==18.6.0" + "version": "==18.6.1" }, "favicon": { "hashes": [ @@ -830,34 +830,34 @@ }, "pyarrow": { "hashes": [ - "sha256:1cbcfcbb0e74b4d94f0b7dde447b835a01bc1d16510edb8bb7d6224b9bf5bafc", - "sha256:25aa11c443b934078bfd60ed63e4e2d42461682b5ac10f67275ea21e60e6042c", - "sha256:2d53ba72917fdb71e3584ffc23ee4fcc487218f8ff29dd6df3a34c5c48fe8c06", - "sha256:2d942c690ff24a08b07cb3df818f542a90e4d359381fbff71b8f2aea5bf58841", - "sha256:2f51dc7ca940fdf17893227edb46b6784d37522ce08d21afc56466898cb213b2", - "sha256:362a7c881b32dc6b0eccf83411a97acba2774c10edcec715ccaab5ebf3bb0835", - "sha256:3e99be85973592051e46412accea31828da324531a060bd4585046a74ba45854", - "sha256:40bb42afa1053c35c749befbe72f6429b7b5f45710e85059cdd534553ebcf4f2", - "sha256:410624da0708c37e6a27eba321a72f29d277091c8f8d23f72c92bada4092eb5e", - "sha256:41a1451dd895c0b2964b83d91019e46f15b5564c7ecd5dcb812dadd3f05acc97", - "sha256:5461c57dbdb211a632a48facb9b39bbeb8a7905ec95d768078525283caef5f6d", - "sha256:69309be84dcc36422574d19c7d3a30a7ea43804f12552356d1ab2a82a713c418", - "sha256:7c28b5f248e08dea3b3e0c828b91945f431f4202f1a9fe84d1012a761324e1ba", - "sha256:8f40be0d7381112a398b93c45a7e69f60261e7b0269cc324e9f739ce272f4f70", - "sha256:a37bc81f6c9435da3c9c1e767324ac3064ffbe110c4e460660c43e144be4ed85", - "sha256:aaee8f79d2a120bf3e032d6d64ad20b3af6f56241b0ffc38d201aebfee879d00", - "sha256:ad42bb24fc44c48f74f0d8c72a9af16ba9a01a2ccda5739a517aa860fa7e3d56", - "sha256:ad7c53def8dbbc810282ad308cc46a523ec81e653e60a91c609c2233ae407689", - "sha256:becc2344be80e5dce4e1b80b7c650d2fc2061b9eb339045035a1baa34d5b8f1c", - "sha256:caad867121f182d0d3e1a0d36f197df604655d0b466f1bc9bafa903aa95083e4", - "sha256:ccbf29a0dadfcdd97632b4f7cca20a966bb552853ba254e874c66934931b9841", - "sha256:da93340fbf6f4e2a62815064383605b7ffa3e9eeb320ec839995b1660d69f89b", - "sha256:e217d001e6389b20a6759392a5ec49d670757af80101ee6b5f2c8ff0172e02ca", - "sha256:f010ce497ca1b0f17a8243df3048055c0d18dcadbcc70895d5baf8921f753de5", - "sha256:f12932e5a6feb5c58192209af1d2607d488cb1d404fbc038ac12ada60327fa34" + "sha256:0846ace49998825eda4722f8d7f83fa05601c832549c9087ea49d6d5397d8cec", + "sha256:0d8b90efc290e99a81d06015f3a46601c259ecc81ffb6d8ce288c91bd1b868c9", + "sha256:0e36425b1c1cbf5447718b3f1751bf86c58f2b3ad299f996cd9b1aa040967656", + "sha256:19c812d303610ab5d664b7b1de4051ae23565f9f94d04cbea9e50569746ae1ee", + "sha256:1b50bb9a82dca38a002d7cbd802a16b1af0f8c50ed2ec94a319f5f2afc047ee9", + "sha256:1d568acfca3faa565d663e53ee34173be8e23a95f78f2abfdad198010ec8f745", + "sha256:23a77d97f4d101ddfe81b9c2ee03a177f0e590a7e68af15eafa06e8f3cf05976", + "sha256:2466be046b81863be24db370dffd30a2e7894b4f9823fb60ef0a733c31ac6256", + "sha256:272f147d4f8387bec95f17bb58dcfc7bc7278bb93e01cb7b08a0e93a8921e18e", + "sha256:280289ebfd4ac3570f6b776515baa01e4dcbf17122c401e4b7170a27c4be63fd", + "sha256:2cc63e746221cddb9001f7281dee95fd658085dd5b717b076950e1ccc607059c", + "sha256:3b97649c8a9a09e1d8dc76513054f1331bd9ece78ee39365e6bf6bc7503c1e94", + "sha256:3d1733b1ea086b3c101427d0e57e2be3eb964686e83c2363862a887bb5c41fa8", + "sha256:5b0810864a593b89877120972d1f7af1d1c9389876dbed92b962ed81492d3ffc", + "sha256:7a7b6a765ee4f88efd7d8348d9a1f804487d60799d0428b6ddf3344eaef37282", + "sha256:7b5b9f60d9ef756db59bec8d90e4576b7df57861e6a3d6a8bf99538f68ca15b3", + "sha256:92fb031e6777847f5c9b01eaa5aa0c9033e853ee80117dce895f116d8b0c3ca3", + "sha256:993287136369aca60005ee7d64130f9466489c4f7425f5c284315b0a5401ccd9", + "sha256:a1c4fce253d5bdc8d62f11cfa3da5b0b34b562c04ce84abb8bd7447e63c2b327", + "sha256:a7cd32fe77f967fe08228bc100433273020e58dd6caced12627bcc0a7675a513", + "sha256:b99e559d27db36ad3a33868a475f03e3129430fc065accc839ef4daa12c6dab6", + "sha256:bc4ea634dacb03936f50fcf59574a8e727f90c17c24527e488d8ceb52ae284de", + "sha256:d8c26912607e26c2991826bbaf3cf2b9c8c3e17566598c193b492f058b40d3a4", + "sha256:e6be4d85707fc8e7a221c8ab86a40449ce62559ce25c94321df7c8500245888f", + "sha256:ea830d9f66bfb82d30b5794642f83dd0e4a718846462d22328981e9eb149cba8" ], "markers": "python_version >= '3.7'", - "version": "==11.0.0" + "version": "==12.0.0" }, "pydeck": { "hashes": [ @@ -1256,5 +1256,10 @@ "version": "==3.15.0" } }, - "develop": {} + "develop": { + "mock-generators": { + "editable": true, + "path": "." + } + } } diff --git a/mock_generators/__init__.py b/mock_generators/__init__.py index b31ecc4..5699188 100644 --- a/mock_generators/__init__.py +++ b/mock_generators/__init__.py @@ -1,2 +1,3 @@ - +import sys +sys.path.append('.') __version__ = "0.1.0" \ No newline at end of file diff --git a/mock_generators/app.py b/mock_generators/app.py index 7756496..f660485 100644 --- a/mock_generators/app.py +++ b/mock_generators/app.py @@ -4,12 +4,13 @@ from tabs.design_tab import design_tab from tabs.data_importer import data_importer_tab from tabs.tutorial import tutorial_tab -from config import preload_state, load_generators +from config import setup_logging, preload_state, load_generators_to_streamlit # SETUP st.set_page_config(layout="wide") +setup_logging() preload_state() -load_generators() +load_generators_to_streamlit() # UI st.title("Mock Graph Data Generator") diff --git a/mock_generators/config.py b/mock_generators/config.py index b12c8b2..bc461c4 100644 --- a/mock_generators/config.py +++ b/mock_generators/config.py @@ -2,7 +2,14 @@ from constants import * from file_utils import load_json from models.generator import generators_from_json +import logging +def setup_logging(): + logger = logging.getLogger(__name__) + FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s" + logging.basicConfig(format=FORMAT) + logger.setLevel(logging.DEBUG) + def preload_state(): if ZIPS_PATH not in st.session_state: st.session_state[ZIPS_PATH] = DEFAULT_ZIPS_PATH @@ -18,7 +25,6 @@ def preload_state(): st.session_state[IMPORTED_FILENAME] = "" if IMPORTS_PATH not in st.session_state: st.session_state[IMPORTS_PATH] = DEFAULT_IMPORTS_PATH - # TODO: Replace with reference to selected import file if IMPORTED_FILE not in st.session_state: st.session_state[IMPORTED_FILE] = None if IMPORTED_NODES not in st.session_state: @@ -32,7 +38,19 @@ def preload_state(): if MAPPINGS not in st.session_state: st.session_state[MAPPINGS] = None -def load_generators(): +def load_generators( + folderpath = str +): + try: + with open(folderpath) as input: + generators_json = load_json(folderpath) + new_generators = generators_from_json(generators_json) + return new_generators + except FileNotFoundError: + raise Exception('Generator JSONs not found.') + + +def load_generators_to_streamlit(): spec_filepath = st.session_state[SPEC_FILE] generators = st.session_state[GENERATORS] try: diff --git a/mock_generators/constants.py b/mock_generators/constants.py index 28ba3d3..6ec2053 100644 --- a/mock_generators/constants.py +++ b/mock_generators/constants.py @@ -24,6 +24,4 @@ CODE_TEMPLATE_FILE = "templates_file" DEFAULT_DATA_IMPORTER_FILENAME = "neo4j_importer_model" NEW_GENERATOR_ARGS = "new_generator_args" - -# TODO: Can Streamlit's st.session hold all the data we'll be generating? MAPPINGS = "mappings" diff --git a/mock_generators/default_generators.json b/mock_generators/default_generators.json deleted file mode 100644 index c2f320c..0000000 --- a/mock_generators/default_generators.json +++ /dev/null @@ -1,455 +0,0 @@ -{ - "README":{ - "content": "This is a list of all generators that initially came with this repo." - } - , - "05711cac": { - "args": [], - "code_url": "mock_generators/generators/05711cac.py", - "description": "Random URI with Faker library.", - "name": "URL", - "tags": [ - "uri", - "url" - ], - "type": "String" - }, - "05add148": { - "args": [ - { - "default": "", - "label": "Optional Domain (ie: company.com)", - "type": "String" - } - ], - "code_url": "mock_generators/generators/05add148.py", - "description": "Random email with Faker library.", - "name": "Email", - "tags": [ - "email" - ], - "type": "String" - }, - "111d38e0": { - "args": [ - { - "default": "", - "label": "List of float (ie: 1.0, 2.2, 3.3)", - "type": "String" - } - ], - "code_url": "mock_generators/generators/111d38e0.py", - "description": "Randomly selected float from a comma-seperated list of options.", - "name": "Float from list", - "tags": [ - "float", - "list" - ], - "type": "Float" - }, - "338d576e": { - "args": [ - { - "default": 1, - "label": "Minimum Number", - "type": "Integer" - }, - { - "default": 10, - "label": "Maximum Number", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/338d576e.py", - "description": "String generator using the lorem-text package", - "name": "Paragraphs", - "tags": [ - "string", - "lorem", - "ipsum", - "paragraph", - "paragraphs" - ], - "type": "String" - }, - "469b37c7": { - "args": [ - { - "default": 1, - "label": "Min", - "type": "Integer" - }, - { - "default": 10, - "label": "Max", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/469b37c7.py", - "description": "Random integer from a min and max value argument. Argument values are inclusive.", - "name": "Int Range", - "tags": [ - "int", - "integer", - "number", - "num", - "count" - ], - "type": "Integer" - }, - "470ff56f": { - "args": [], - "code_url": "mock_generators/generators/470ff56f.py", - "description": "Country name generator using the Faker library.", - "name": "Country", - "tags": [ - "country", - "from" - ], - "type": "String" - }, - "4b0db60a": { - "args": [], - "code_url": "mock_generators/generators/4b0db60a.py", - "description": "Randomly assigns to a target node. Duplicates and orphan nodes possible.", - "name": "Pure Random", - "tags": [ - "random" - ], - "type": "Assignment" - }, - "57f2df99": { - "args": [ - { - "default": 50, - "label": "Percent chance of true (out of 100)", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/57f2df99.py", - "description": "Bool generator using the Faker library.", - "name": "Bool", - "tags": [ - "bool", - "boolean" - ], - "type": "Bool" - }, - "58e9ddbb": { - "args": [], - "code_url": "mock_generators/generators/58e9ddbb.py", - "description": "First name generator using the Faker library", - "name": "First Name", - "tags": [ - "first", - "name" - ], - "type": "String" - }, - "5929c11b": { - "args": [], - "code_url": "mock_generators/generators/5929c11b.py", - "description": "Last name generator using the Faker library.", - "name": "Last Name", - "tags": [ - "last", - "name" - ], - "type": "String" - }, - "5bf1fbd6": { - "args": [ - { - "default": "", - "label": "List of words (ie: alpha, brave, charlie)", - "type": "String" - } - ], - "code_url": "mock_generators/generators/5bf1fbd6.py", - "description": "Randomly selected string from a comma-seperated list of options.", - "name": "String from list", - "tags": [ - "string", - "list", - "word", - "words", - "status", - "type" - ], - "type": "String" - }, - "5e30c30b": { - "args": [], - "code_url": "mock_generators/generators/5e30c30b.py", - "description": "Company name generator using the Faker library.", - "name": "Company Name", - "tags": [ - "company", - "name" - ], - "type": "String" - }, - "73853311": { - "args": [], - "code_url": "mock_generators/generators/73853311.py", - "description": "Assigns each source node to a random target node, until target node records are exhausted. No duplicates, no orphan to nodes.", - "name": "Exhaustive Random", - "tags": [ - "exhaustive" - ], - "type": "Assignment" - }, - "78bc0765": { - "args": [ - { - "default": 37, - "label": "Limit character length", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/78bc0765.py", - "description": "Random UUID 4 hash using Faker library. 37 Characters Max.", - "name": "UUID", - "tags": [ - "uuid", - "hash", - "unique", - "uid" - ], - "type": "String" - }, - "92eeddbb": { - "args": [], - "code_url": "mock_generators/generators/92eeddbb.py", - "description": "City name generator using the Faker library.", - "name": "City", - "tags": [ - "city", - "name" - ], - "type": "String" - }, - "ab64469b": { - "args": [ - { - "default": "1970-01-01", - "label": "Oldest Date", - "type": "Datetime" - }, - { - "default": "2022-11-24", - "label": "Newest Date", - "type": "Datetime" - } - ], - "code_url": "mock_generators/generators/ab64469b.py", - "description": "Generate a random date between 2 specified dates. Exclusive of days specified.", - "name": "Date", - "tags": [ - "date", - "datetime", - "created", - "updated", - "at" - ], - "type": "Datetime" - }, - "c9a071b5": { - "args": [], - "code_url": "mock_generators/generators/c9a071b5.py", - "description": "Technobabble words all lower-cased. Faker Library", - "name": "Technical BS Phrase", - "tags": [ - "phrase", - "phrases", - "technical", - "jargon", - "task", - "description" - ], - "type": "String" - }, - "d1ebdc1a": { - "args": [], - "code_url": "mock_generators/generators/d1ebdc1a.py", - "description": "Phrase with first letter capitalized. Faker Library", - "name": "Catch Phrase", - "tags": [ - "phrase", - "phrases", - "catch", - "project", - "description" - ], - "type": "String" - }, - "df2bbd43": { - "args": [ - { - "default": "", - "label": "CSV Filepath", - "type": "String", - "hint": "mock_generators/datasets/tech_companies.csv", - "description":"" - }, - { - "default": "", - "label": "Header Column Field", - "type": "String", - "hint": "Company Name", - "description":"" - } - ], - "code_url": "mock_generators/generators/df2bbd43.py", - "description": "Random string row value from a specified csv file. Be certain field contains string values.", - "name": "String from CSV", - "tags": [ - "csv", - " string", - " random" - ], - "type": "String" - }, - "e0eb78b0": { - "args": [ - { - "default": 33, - "label": "Limit Character Length", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/e0eb78b0.py", - "description": "Random MD5 hash using Faker library. 33 Characters max", - "name": "MD5", - "tags": [ - "md5", - "hash", - "unique" - ], - "type": "String" - }, - "e56d87a3": { - "args": [ - { - "default": "", - "label": "List of integers (ie: 1, 2, 3)", - "type": "String" - } - ], - "code_url": "mock_generators/generators/e56d87a3.py", - "description": "Randomly selected int from a comma-seperated list of options. If no list provided, will return 0", - "name": "Int from list", - "tags": [ - "int", - "integer", - "number", - "num", - "count", - "list", - "salary", - "cost" - ], - "type": "Integer" - }, - "e8cff8c1": { - "args": [ - { - "default": 0.0, - "label": "Min", - "type": "Float" - }, - { - "default": 1.0, - "label": "Max", - "type": "Float" - }, - { - "default": 2, - "label": "Decimal Places", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/e8cff8c1.py", - "description": "Random float between a range. Inclusive.", - "name": "Float", - "tags": [ - "float", - "decimal", - "number", - "num", - "count", - "cost", - "price" - ], - "type": "Float" - }, - "ecdff22c": { - "args": [ - { - "default": 1, - "label": "Value", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/ecdff22c.py", - "description": "Constant integer value", - "name": "Int", - "tags": [ - "int", - "integer", - "num", - "number", - "count" - ], - "type": "Integer" - }, - "id1": { - "args": [ - { - "default": 1, - "label": "Minimum Number", - "type": "Integer" - }, - { - "default": 10, - "label": "Maximum Number", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/loremtext_words.py", - "description": "String generator using the lorem-text package", - "name": "Words", - "tags": [ - "words", - "lorem", - "text", - "description" - ], - "type": "String" - }, - "id2": { - "args": [ - { - "default": 1, - "label": "Minimum Number", - "type": "Integer" - }, - { - "default": 10, - "label": "Maximum Number", - "type": "Integer" - } - ], - "code_url": "mock_generators/generators/loremtext_sentence.py", - "description": "String generator using the lorem-text package", - "name": "Sentences", - "tags": [ - "sentence", - "sentences", - "lorem", - "text", - "description" - ], - "type": "String" - } -} \ No newline at end of file diff --git a/mock_generators/file_utils.py b/mock_generators/file_utils.py index f2b3ad8..072b5a3 100644 --- a/mock_generators/file_utils.py +++ b/mock_generators/file_utils.py @@ -78,7 +78,6 @@ def save_json(filepath, data): def save_csv(filepath: str, data: list[dict]): with open(filepath, 'w') as csvfile: - # TODO: Fix, will crash if the data is empty if data is None: raise Exception(f'file_utils.py: save_csv: No data to save to {filepath}') if len(data) == 0: diff --git a/mock_generators/generate_mapping.py b/mock_generators/generate_mapping.py deleted file mode 100644 index 9d5b738..0000000 --- a/mock_generators/generate_mapping.py +++ /dev/null @@ -1,272 +0,0 @@ -# Builds mapping file from specially formatted arrows.app JSON file - -import json -from models.mapping import Mapping -from models.node_mapping import NodeMapping -from models.relationship_mapping import RelationshipMapping -from models.property_mapping import PropertyMapping -from models.generator import Generator -import logging -import uuid - -def generator_for_raw_property( - property_value: str, - generators: dict[str, Generator] - ) -> tuple[Generator, list[any]]: - """Returns a generator and args for a property""" - # Sample expected string: "{\"company_name\":[]}" - - # Check that the property info is notated for mock data generation use - # leading_bracket = property_value[0] - # trailing_bracket = property_value[-1] - # if leading_bracket != "{" or trailing_bracket != "}": - # logging.warning(f'generate_mapping.py: generator_for_raw_property: property_value not wrapped in {{ and }}. Skipping generator assignment for property_value: {property_value}') - # return (None, None) - - # # The property value should be a JSON string. Convert to a dict obj - # json_string = property_value[1:-1] - - try: - obj = json.loads(property_value) - except Exception as e: - logging.info(f'generate_mapping.py: generator_for_raw_property: Could not parse JSON string: {property_value}. Skipping generator assignment for property_value: {property_value}') - return (None, None) - - # Should only ever be one - for key, value in obj.items(): - generator_id = key - generator = generators.get(generator_id, None) - if generator is None: - logging.error(f'generate_mapping.py: generator_for_raw_property: generator_id {generator_id} not found in generators. Skipping generator assignment for property_value: {property_value}') - return None - - args = value - return (generator, args) - -def propertymappings_for_raw_properties( - raw_properties: dict[str, str], - generators: dict[str, Generator] - ) -> dict[str, PropertyMapping]: - """Returns a list of property mappings for a node or relationship""" - property_mappings = {} - for key, value in raw_properties.items(): - generator, args = generator_for_raw_property(value, generators) - if generator is None: - # TODO: Insert PropertyMapping with no generator? Use literal value? - continue - - pid = str(uuid.uuid4())[:8] - property_mapping = PropertyMapping( - pid = pid, - name=key, - generator=generator, - args=args - ) - property_mappings[pid] = property_mapping - return property_mappings - -def node_mappings_from( - node_dicts: list, - generators: dict[str, Generator] - ) -> dict[str, NodeMapping]: - """Converts node information from JSON file to mapping objects""" - # Sample node_dict - # { - # "id": "n1", - # "position": { - # "x": 284.5, - # "y": -204 - # }, - # "caption": "Company", - # "labels": [], - # "properties": { - # "name": "{{\"company_name\":[]}}", - # "uuid": "{{\"uuid\":[8]}}", - # "{{count}}": "{{\"int\":[1]}}", - # "{{key}}": "uuid" - # }, - # "style": {} - # } - - node_mappings = {} - for node_dict in node_dicts: - - # Check for required data in raw node dict from arrows.app json - position = node_dict.get("position", None) - if position is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing position key from: {node_dict}: Skipping {node_dict}") - continue - - caption = node_dict.get("caption", None) - if caption is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing caption key from: {node_dict}: Skipping {node_dict}") - continue - - - # Check for required properties dict - properties = node_dict.get("properties", None) - if properties is None: - logging.warning(f"generate_mappings: node_mappings_from: dict is missing properties: {node_dict}. Can not configure for data generation. Skipping node.") - continue - - # Determine count generator to use - count_generator_config = properties.get("{count}", None) - if count_generator_config is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing {{count}} key from properties: {properties}: Skipping {node_dict}") - continue - - # Get string name for key property. Value should be an unformatted string - key = properties.get("{key}", None) - if key is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing {{key}}: Skipping {node_dict}") - continue - - # Get proper generators for count generator - count_generator, count_args = generator_for_raw_property(count_generator_config, generators) - - # Create property mappings for properties - property_mappings = propertymappings_for_raw_properties(properties, generators) - - # Assign correct property mapping as key property - # logging.info(f'generate_mappings: node_mappings_from: key_property: {key}, property_mappings: {property_mappings}') - key_property = next((v for k,v in property_mappings.items() if v.name == key), None) - if key_property is None: - logging.warning(f"generate_mappings: node_mappings_from: No key property mapping found for node: {node_dict} - key name: {key}. Skipping node.") - continue - - # Create node mapping - node_mapping = NodeMapping( - nid = node_dict["id"], - labels = node_dict["labels"], - properties = property_mappings, - count_generator=count_generator, - count_args=count_args, - key_property=key_property, - position = position, - caption=caption - ) - # TODO: - # Run generators - node_mappings[node_mapping.nid] = node_mapping - return node_mappings - -def relationshipmappings_from( - relationship_dicts: list[dict], - nodes: dict[str, NodeMapping], - generators: dict[str, Generator] - ) -> dict[str,RelationshipMapping]: - # Sample relationship_dict - # { - # "id": "n0", - # "fromId": "n1", - # "toId": "n0", - # "type": "EMPLOYS", - # "properties": { - # "{count}": "{\"int\":[10]}", - # "{assignment}": "{\"exhaustive_random\":[]}", - # "{filter}": "{string_from_list:[]}" - # }, - # "style": {} - # }, - relationshipmappings = {} - for relationship_dict in relationship_dicts: - # Check for required data in raw node dict from arrows.app json - - rid = relationship_dict.get("id", None) - if rid is None: - logging.warning(f"generate_mappings: relationshipmappings_from: relationship properties is missing 'id' key from: {relationship_dict}: Skipping {relationship_dict}") - continue - type = relationship_dict.get("type", None) - if type is None: - logging.warning(f"generate_mappings: relationshipmappings_from: relationship properties is missing 'type' key from: {relationship_dict}: Skipping {relationship_dict}") - continue - from_id = relationship_dict.get("fromId", None) - if from_id is None: - logging.warning(f"generate_mappings: relationshipmappings_from: relationship properties is missing 'fromId' key from: {relationship_dict}: Skipping {relationship_dict}") - continue - - to_id = relationship_dict.get("toId", None) - if to_id is None: - logging.warning(f"generate_mappings: relationshipmappings_from: relationship properties is missing 'toId' key from: {relationship_dict}: Skipping {relationship_dict}") - continue - - # Check for required properties dict - properties = relationship_dict.get("properties", None) - if properties is None: - logging.warning(f"generate_mappings: relationshipmappings_from: dict is missing properties: {relationship_dict}. Can not configure for data generation. Skipping relationship.") - continue - - # Determine count generator to use - count_generator_config = properties.get("{count}", None) - if count_generator_config is None: - logging.warning(f"generate_mappings: relationshipmappings_from: relationship properties is missing '{{count}}' key from properties: {properties}: Skipping {relationship_dict}") - continue - - assignment_generator_config = properties.get("{assignment}", None) - # If missing, use ExhaustiveRandom - if assignment_generator_config is None: - assignment_generator_config = "{\"exhaustive_random\":[]}" - - # Get proper generators for count generator - count_generator, count_args = generator_for_raw_property(count_generator_config, generators) - - # Create property mappings for properties - property_mappings = propertymappings_for_raw_properties(properties, generators) - - assignment_generator, assignment_args = generator_for_raw_property(assignment_generator_config, generators) - - from_node = nodes.get(from_id, None) - if from_node is None: - logging.warning(f"generate_mappings: relationshipmappings_from: No node mapping found for relationship: {relationship_dict} - fromId: {from_id}. Skipping relationship.") - continue - - to_node = nodes.get(to_id, None) - if to_node is None: - logging.warning(f"generate_mappings: relationshipmappings_from: No node mapping found for relationship: {relationship_dict} - toId: {to_id}. Skipping relationship.") - continue - - # Create relationship mapping - relationship_mapping = RelationshipMapping( - rid = rid, - type = type, - from_node = from_node, - to_node = to_node , - count_generator=count_generator, - count_args=count_args, - properties=property_mappings, - assignment_generator= assignment_generator, - assignment_args=assignment_args - ) - relationshipmappings[relationship_mapping.rid] = relationship_mapping - - return relationshipmappings - - -def mapping_from_json( - json_file: str, - generators: list[Generator]) -> Mapping: - - try: - # Validate json file - loaded_json = json.loads(json_file) - except Exception as e: - raise Exception(f"generate_mappings: mapping_from_json: Error loading JSON file: {e}") - - # Extract and process nodes - node_dicts = loaded_json.get("nodes", None) - if node_dicts is None: - raise Exception(f"generate_mappings: mapping_from_json: No nodes found in JSON file: {json}") - relationship_dicts = loaded_json.get("relationships", None) - if relationship_dicts is None: - raise Exception(f"generate_mappings: mapping_from_json: No relationships found in JSON file: {json}") - - # Convert source information to mapping objects - nodes = node_mappings_from(node_dicts, generators) - relationships = relationshipmappings_from(relationship_dicts, nodes, generators) - - # Create mapping object - mapping = Mapping( - nodes=nodes, - relationships=relationships - ) - return mapping diff --git a/mock_generators/generators/ab64469b.py b/mock_generators/generators/ab64469b.py deleted file mode 100644 index 278ee21..0000000 --- a/mock_generators/generators/ab64469b.py +++ /dev/null @@ -1,15 +0,0 @@ -from datetime import timedelta -import random - -# Do not change function name or arguments -def generate(args: list[any]): - # oldest ISO datetime - min = args[0] - # most recent ISO datetime - max = args[1] - between = max - min - days = between.days - random.seed(a=None) - random_days = random.randrange(days) - result = min + timedelta(days=random_days) - return result \ No newline at end of file diff --git a/mock_generators/generators/date.py b/mock_generators/generators/date.py new file mode 100644 index 0000000..6ef31f2 --- /dev/null +++ b/mock_generators/generators/date.py @@ -0,0 +1,19 @@ +from datetime import datetime, timedelta +import random + +# Do not change function name or arguments +def generate(args: list[any]): + # oldest ISO datetime + min = args[0] + # most recent ISO datetime + max = args[1] + + # Convert string args to datetime + min_date = datetime.fromisoformat(min) + max_date = datetime.fromisoformat(max) + + delta = max_date - min_date + result = min_date + timedelta(seconds=random.randint(0, delta.total_seconds())) + + # Data Importer expects a string in ISO format + return result.isoformat() \ No newline at end of file diff --git a/mock_generators/generators/e56d87a3.py b/mock_generators/generators/e56d87a3.py deleted file mode 100644 index 1c3d375..0000000 --- a/mock_generators/generators/e56d87a3.py +++ /dev/null @@ -1,11 +0,0 @@ -import random - -# Do not change function name or arguments -def generate(args: list[any]): - options = args[0] - if options is None or options == "": - return 0 - options = options.replace(' ', '') - options = options.split(",") - result = int(random.choice(options)) - return result \ No newline at end of file diff --git a/mock_generators/generators/ecdff22c.py b/mock_generators/generators/float.py similarity index 57% rename from mock_generators/generators/ecdff22c.py rename to mock_generators/generators/float.py index 0a1ee03..f269384 100644 --- a/mock_generators/generators/ecdff22c.py +++ b/mock_generators/generators/float.py @@ -1,6 +1,6 @@ # Do not change function name or arguments def generate(args: list[any]): value = args[0] - return value - - + # Will raise error if not an int + f = float(value) + return f \ No newline at end of file diff --git a/mock_generators/generators/111d38e0.py b/mock_generators/generators/float_from_list.py similarity index 100% rename from mock_generators/generators/111d38e0.py rename to mock_generators/generators/float_from_list.py diff --git a/mock_generators/generators/e8cff8c1.py b/mock_generators/generators/float_range.py similarity index 100% rename from mock_generators/generators/e8cff8c1.py rename to mock_generators/generators/float_range.py diff --git a/mock_generators/generators/int.py b/mock_generators/generators/int.py new file mode 100644 index 0000000..a69b8cb --- /dev/null +++ b/mock_generators/generators/int.py @@ -0,0 +1,6 @@ +# Do not change function name or arguments +def generate(args: list[any]): + value = args[0] + # Will raise error if not an int + integer = int(value) + return integer \ No newline at end of file diff --git a/mock_generators/generators/int_from_list.py b/mock_generators/generators/int_from_list.py new file mode 100644 index 0000000..ec99917 --- /dev/null +++ b/mock_generators/generators/int_from_list.py @@ -0,0 +1,21 @@ +import random +import logging + +# Do not change function name or arguments +def generate(args: list[any]): + try: + # List should come in wrapped as a string: ie ["1,2,3"] + options = args[0] + if options is None or options == "": + # No args given + return 0 + + # Strip white spaces + # options = options.join(options.split()) + options = options.replace(' ', '') + options = options.split(',') + result = int(random.choice(options)) + return result + except Exception as e: + logging.error(f'Exception: {e}') + return None \ No newline at end of file diff --git a/mock_generators/generators/469b37c7.py b/mock_generators/generators/int_range.py similarity index 100% rename from mock_generators/generators/469b37c7.py rename to mock_generators/generators/int_range.py diff --git a/mock_generators/generators/literal.py b/mock_generators/generators/literal.py new file mode 100644 index 0000000..be0a8c8 --- /dev/null +++ b/mock_generators/generators/literal.py @@ -0,0 +1,4 @@ + +def generate(args: list[any]): + value = args[0] + return value \ No newline at end of file diff --git a/mock_generators/generators/loremtext_sentence.py b/mock_generators/generators/lorem_sentences.py similarity index 100% rename from mock_generators/generators/loremtext_sentence.py rename to mock_generators/generators/lorem_sentences.py diff --git a/mock_generators/generators/loremtext_words.py b/mock_generators/generators/lorem_words.py similarity index 100% rename from mock_generators/generators/loremtext_words.py rename to mock_generators/generators/lorem_words.py diff --git a/mock_generators/generators/78bc0765.py b/mock_generators/generators/short_uuid.py similarity index 93% rename from mock_generators/generators/78bc0765.py rename to mock_generators/generators/short_uuid.py index c42796b..d973677 100644 --- a/mock_generators/generators/78bc0765.py +++ b/mock_generators/generators/short_uuid.py @@ -1,6 +1,5 @@ from faker import Faker fake = Faker() -import logging def generate(args: list[any]=[]): if len(args) == 0: diff --git a/mock_generators/generators/5bf1fbd6.py b/mock_generators/generators/string_from_list.py similarity index 100% rename from mock_generators/generators/5bf1fbd6.py rename to mock_generators/generators/string_from_list.py diff --git a/mock_generators/generators/uuid.py b/mock_generators/generators/uuid.py new file mode 100644 index 0000000..ba034ef --- /dev/null +++ b/mock_generators/generators/uuid.py @@ -0,0 +1,5 @@ +import uuid + +def generate(args: list[any]): + return str(uuid.uuid4()) + \ No newline at end of file diff --git a/mock_generators/logic/generate_mapping.py b/mock_generators/logic/generate_mapping.py index 7025b22..a363a1d 100644 --- a/mock_generators/logic/generate_mapping.py +++ b/mock_generators/logic/generate_mapping.py @@ -6,34 +6,10 @@ from models.relationship_mapping import RelationshipMapping from models.property_mapping import PropertyMapping from models.generator import Generator +from logic.generate_values import generator_for_raw_property import logging import uuid -def generator_for_raw_property( - property_value: str, - generators: dict[str, Generator] - ) -> tuple[Generator, list[any]]: - """Returns a generator and args for specially formatted property values from the arrows.app JSON file""" - # Sample expected string: "{\"company_name\":[]}" - - # Throws an error if a generator can not be found - - obj = json.loads(property_value) - - if len(obj) == 0: - raise Exception(f'generate_mapping.py: generator_for_raw_property: Expected dictionary object from json string not found: {property_value}') - - generator = None - args = None - # Should only be one item, if not take the last - for key, value in obj.items(): - generator_id = key - generator = generators.get(generator_id, None) - if generator is None: - raise Exception(f'generate_mapping.py: generator_for_raw_property: generator_id {generator_id} not found in generators.') - args = value - return (generator, args) - def propertymappings_for_raw_properties( raw_properties: dict[str, str], generators: dict[str, Generator] @@ -50,21 +26,19 @@ def propertymappings_for_raw_properties( property_mappings = {} - if raw_properties is None or len(raw_properties) == 0: - raise Exception(f'generate_mapping.py: propertymappings_for_raw_properties: No raw_properties assignment received.') - if generators is None or len(generators) == 0: raise Exception(f'generate_mapping.py: propertymappings_for_raw_properties: No generators assignment received.') for key, value in raw_properties.items(): + # Skip any keys with { } (brackets) as these are special cases for defining count/assignment/filter generators if key.startswith('{') and key.endswith('}'): continue - # Only process values with string { } (brackets) - if not isinstance(value, str) or not value.startswith('{') or not value.endswith('}'): - property_mappings[key] = value - continue + # TODO: Skip special COUNT and KEY literals + if key == "COUNT" or key == "KEY": + continue + try: generator, args = generator_for_raw_property(value, generators) if generator is None: @@ -89,7 +63,7 @@ def node_mappings_from( node_dicts: list, generators: dict[str, Generator] ) -> dict[str, NodeMapping]: - """Converts node information from JSON file to mapping objects""" + """Converts node information from arrows JSON file to mapping objects""" # Sample node_dict # { # "id": "n1", @@ -108,58 +82,63 @@ def node_mappings_from( # "style": {} # } + # Prepare a dict to store mappings. Incoming node id to be keys node_mappings = {} + + # Process each incoming node data for node_dict in node_dicts: - # Check for required data in raw node dict from arrows.app json + # Incoming data validation position = node_dict.get("position", None) if position is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing position key from: {node_dict}: Skipping {node_dict}") + logging.warning(f"Node properties is missing position key from: {node_dict}: Skipping {node_dict}") continue caption = node_dict.get("caption", None) if caption is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing caption key from: {node_dict}: Skipping {node_dict}") + logging.warning(f"Node properties is missing caption key from: {node_dict}: Skipping {node_dict}") continue + # Check for optional properties dict + properties = node_dict.get("properties", {}) + # Always add a _uid property to node properties + properties["_uid"] = "{\"uuid\":[]}" - # Check for required properties dict - properties = node_dict.get("properties", None) - if properties is None: - logging.warning(f"generate_mappings: node_mappings_from: dict is missing properties: {node_dict}. Can not configure for data generation. Skipping node.") + # Create property mappings for properties + try: + property_mappings = propertymappings_for_raw_properties(properties, generators) + except Exception as e: + logging.warning(f"Could not create property mappings for node: {node_dict}: {e}") continue # Determine count generator to use - count_generator_config = properties.get("{count}", None) + # TODO: Support COUNT literal + count_generator_config = properties.get("COUNT", None) if count_generator_config is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing {{count}} key from properties: {properties}: Skipping {node_dict}") - continue - - # Get string name for key property. Value should be an unformatted string - key = properties.get("{key}", None) - if key is None: - logging.warning(f"generate_mappings: node_mappings_from: node properties is missing {{key}}: Skipping {node_dict}") - continue + count_generator_config = properties.get("{count}", None) + if count_generator_config is None: + count_generator_config = '{"int_range": [1,100]}' + logging.info(f"node properties is missing COUNT or {{count}} key from properties: {properties}: Using defalt int_range generator") # Get proper generators for count generator try: count_generator, count_args = generator_for_raw_property(count_generator_config, generators) except Exception as e: - logging.warning(f"generate_mappings: node_mappings_from: could not find count generator for node: {node_dict}: {e}") + logging.warning(f"Could not find count generator for node: {node_dict}: {e}") continue - # Create property mappings for properties - try: - property_mappings = propertymappings_for_raw_properties(properties, generators) - except Exception as e: - logging.warning(f"generate_mappings: node_mappings_from: could not create property mappings for node: {node_dict}: {e}") - continue + # Get string name for key property. Value should be an unformatted string + key = properties.get("KEY", None) + if key is None: + key = properties.get("{key}", None) + if key is None: + key = "_uid" + logging.info(f"node properties is missing KEY or {{key}}: Assigning self generated _uid") # Assign correct property mapping as key property - # logging.info(f'generate_mappings: node_mappings_from: key_property: {key}, property_mappings: {property_mappings}') key_property = next((v for k,v in property_mappings.items() if v.name == key), None) if key_property is None: - logging.warning(f"generate_mappings: node_mappings_from: No key property mapping found for node: {node_dict} - key name: {key}. Skipping node.") + logging.warning(f"Key property mapping not found for node: {node_dict} - key name: {key}. Skipping node.") continue # Create node mapping @@ -173,8 +152,6 @@ def node_mappings_from( position = position, caption=caption ) - # TODO: - # Run generators node_mappings[node_mapping.nid] = node_mapping return node_mappings @@ -219,16 +196,16 @@ def relationshipmappings_from( continue # Check for required properties dict - properties = relationship_dict.get("properties", None) - if properties is None: - logging.warning(f"generate_mappings: relationshipmappings_from: dict is missing properties: {relationship_dict}. Can not configure for data generation. Skipping relationship.") - continue + properties = relationship_dict.get("properties", {}) # Determine count generator to use - count_generator_config = properties.get("{count}", None) + # TODO: Support COUNT key type + count_generator_config = properties.get("COUNT", None) if count_generator_config is None: - logging.warning(f"generate_mappings: relationshipmappings_from: relationship properties is missing '{{count}}' key from properties: {properties}: Skipping {relationship_dict}") - continue + count_generator_config = properties.get("{count}", None) + if count_generator_config is None: + count_generator_config = '{"int_range": [1,3]}' + logging.info(f"Relationship properties is missing COUNT or '{{count}}' key from properties: {properties}: Using default int_range generator") assignment_generator_config = properties.get("{assignment}", None) # If missing, use ExhaustiveRandom diff --git a/mock_generators/logic/generate_values.py b/mock_generators/logic/generate_values.py new file mode 100644 index 0000000..66d1435 --- /dev/null +++ b/mock_generators/logic/generate_values.py @@ -0,0 +1,239 @@ + +from models.generator import Generator +import logging +import json + +# ORIGINAL GENERATOR ASSIGNMENT +def actual_generator_for_raw_property( + property_value: str, + generators: dict[str, Generator] + ) -> tuple[Generator, list[any]]: + """Returns a generator and args for a property. Returns None if not found.""" + # Sample property_values: + # "{\"company_name\":[]}" + # {"int_from_list": ["1,2,3"]} + try: + obj = json.loads(property_value) + # Should only ever be one + for key, value in obj.items(): + generator_id = key + generator = generators.get(generator_id, None) + if generator is None: + logging.error(f'Generator_id {generator_id} not found in generators. Skipping generator assignment for property_value: {property_value}') + return (None, None) + args = value + return (generator, args) + except Exception as e: + logging.debug(f'Could not parse JSON string: {property_value}. Returning None from generator assignment for property_value: {property_value}. Error: {e}') + + return (None, None) + +# KEYWORD GENERATOR ASSIGNMENT +def keyword_generator_for_raw_property( + value: str, + generators: dict[str, Generator] + ) -> tuple[Generator, list[any]]: + """Returns a generator and args for a property value using a generic keyword. Returns None if not found.""" + + result = None + if value.lower() == "string": + result = { + "lorem_words": [1,3] + } + elif value.lower() == "int" or value.lower() == "integer": + result = { + "int_range": [1,100] + } + elif value.lower() == "float": + result = { + "float_range": [1.0,100.0, 2] + } + elif value.lower() == "bool" or value.lower() == "boolean": + result = { + "bool": [50] + } + elif value.lower() == "date" or value.lower() == "datetime": + result = { + "date": ["1970-01-01", "2022-11-24"] + } + + # Default + if result is None: + return (None, None) + + # Generator was assigned + g_config = json.dumps(result) + return actual_generator_for_raw_property(g_config, generators) + + +# LITERAL GENERATOR ASSIGNMENT SUPPORT +def all_ints(values: list[str]) -> bool: + for value in values: + if not is_int(value): + return False + return True + +def some_floats(values: list[str]) -> bool: + for value in values: + if is_float(value): + return True + return False + +def all_floats(values: list[str]) -> bool: + for value in values: + if not is_float(value): + return False + return True + +def is_int(value: str) -> bool: + try: + int(value) + return True + except ValueError: + return False + +def is_float(value: str) -> bool: + try: + f = float(value) + return True + except ValueError: + return False + +def literal_generator_from_value( + value: str, + generators: list[Generator] + )-> tuple[Generator, list[any]]: + """ + Attempts to find an actual generator based on more concise literal values from arrows.app JSON + + Support for: + - ints + - floats + - ranges of ints + - ranges of floats + - lists of ints + - lists of floats + - string literals + - list of strings + + TODO: + - bools + - lists of bools + - date + - lists of dates + - datetime + - list of datetimes + - "int" / "integer" -> random int generator + - "float" -> random float generator + - "string" -> random word generator + - "bool" / "boolean" -> random bool generator + - "date" -> random datetime generator + - "datetime" -> random datetime generator + """ + # Sample expected values: + # "1" + # "1-10" + # "[3, 5, 10]" + # "[Yes, No]" + + # Original specificaion took stringified JSON objects to notate generator and args to use. We're going to convert matching literal values to appropriate generators + + # Default is to use the literal generator + result = { + "string": [value] + } + + # Check if value is an int or float + if is_int(value): + integer = int(value) + result = { + "int": [integer] + } + + if is_float(value): + f = float(value) + result = { + "float": [f] + } + + # NOTE: Not currently handling complex literals + + # Check if value is a range of ints or floats + r = value.split("-") + if len(r) == 2: + # Single dash in string, possibly a literal range + values = [r[0], r[1]] + if all_ints(values): + result = { + "int_range": [int(r[0]), int(r[1])] + } + elif some_floats(values): + # Float range function expects 3 args - this one seems more sensible than other functions + result = { + "float_range": [float(r[0]), float(r[1]), 2] + } + + + # Check for literal list of ints, floats, or strings + if value.startswith('[') and value.endswith(']'): + values = value[1:-1].split(',') + # Generators take a strange format where the args are always a string - including # lists of other data, like ints, floats. ie ["1,2,3"] is an expected arg type + # because certain generators could take multiple args from different text fields + # These literals, however, all only take a single generic arg + + # YES - this is terrible + + if all_ints(values): + ints_as_string = ",".join([f'{int(v)}' for v in values]) + result = { + "int_from_list": [f"{ints_as_string}"] + } + elif some_floats(values): + floats_as_string = ",".join([f'{float(v)}' for v in values]) + result = { + "float_from_list": [f"{floats_as_string}"] + } + else: + result = { + "string_from_list": values + } + + # Package and return from legacy process + actual_string = json.dumps(result) + return actual_generator_for_raw_property(actual_string, generators) + +def generator_for_raw_property( + property_value: str, + generators: dict[str, Generator] + ) -> tuple[Generator, list[any]]: + """ + Returns a generator and args for specially formatted property values from the arrows.app JSON file. Attempts to determine if literal or original generator + specification was use. + + Return None if no generator found. + """ + + # Original Sample expected string: "{\"company_name\":[]}" + # New literal examples: + # "{\"company_name\":\"Acme\"}" + # "{\"company_name\":[\"Acme\"]}" + # "{\"company_name\":\"string\"}" + + # Also fupport following options: "string", "bool", "boolean", "float", "integer", "number", "date", "datetime" + + generator, args = None, None + + # Check for Legacy Properties assignments + # Also returns None, None if no matching generator found + if generator is None: + generator, args = keyword_generator_for_raw_property(property_value, generators) + + if generator is None: + generator, args = actual_generator_for_raw_property(property_value, generators) + + # Check for new literal assignments + # Defaults to string literal + if generator is None: + generator, args = literal_generator_from_value(property_value, generators) + + return (generator, args) \ No newline at end of file diff --git a/mock_generators/logic/mapping_conversions.py b/mock_generators/logic/mapping_conversions.py new file mode 100644 index 0000000..e9b73c5 --- /dev/null +++ b/mock_generators/logic/mapping_conversions.py @@ -0,0 +1,99 @@ +# For converting data from various forms to other forms +from streamlit_agraph import agraph, Node, Edge, Config + +def convert_agraph_node_to_arrows_node(node): + # Convert agraph node to arrows node + # TODO: How do we handle position + arrows_node = { + "id": node.id, + "caption": node.label, + "position": { + "x": 0, + "y": 0, + }, + "labels":[], + "style": {}, + "properties": {} + } + return arrows_node + +def convert_agraph_edge_to_arrows_relationship(edge): + arrows_relationship = { + "id": edge.id, + "from": edge.source, + "to": edge.target, + "type": edge.label, + "properties": {}, + "style": {} + } + return arrows_relationship + +def convert_agraph_to_arrows_json(agraph_nodes, agraph_edges): + # Convert agraph to arrows json + arrows_nodes = [], + arrows_relationships = [] + for node in agraph_nodes: + new_node = convert_agraph_node_to_arrows_node(node) + arrows_nodes.append(new_node) + for edge in agraph_edges: + new_relationship = convert_agraph_edge_to_arrows_relationship(edge) + arrows_relationships.append(new_relationship) + arrows_json = { + "nodes": arrows_nodes, + "relationships": arrows_relationships, + "style": { + "font-family": "sans-serif", + "background-color": "#ffffff", + "background-image": "", + "background-size": "100%", + "node-color": "#ffffff", + "border-width": 4, + "border-color": "#000000", + "radius": 50, + "node-padding": 5, + "node-margin": 2, + "outside-position": "auto", + "node-icon-image": "", + "node-background-image": "", + "icon-position": "inside", + "icon-size": 64, + "caption-position": "inside", + "caption-max-width": 200, + "caption-color": "#000000", + "caption-font-size": 50, + "caption-font-weight": "normal", + "label-position": "inside", + "label-display": "pill", + "label-color": "#000000", + "label-background-color": "#ffffff", + "label-border-color": "#000000", + "label-border-width": 4, + "label-font-size": 40, + "label-padding": 5, + "label-margin": 4, + "directionality": "directed", + "detail-position": "inline", + "detail-orientation": "parallel", + "arrow-width": 5, + "arrow-color": "#000000", + "margin-start": 5, + "margin-end": 5, + "margin-peer": 20, + "attachment-start": "normal", + "attachment-end": "normal", + "relationship-icon-image": "", + "type-color": "#000000", + "type-background-color": "#ffffff", + "type-border-color": "#000000", + "type-border-width": 0, + "type-font-size": 16, + "type-padding": 5, + "property-position": "outside", + "property-alignment": "colon", + "property-color": "#000000", + "property-font-size": 16, + "property-font-weight": "normal" + } + } + return arrows_json + \ No newline at end of file diff --git a/mock_generators/mappings/README.md b/mock_generators/mappings/README.md new file mode 100644 index 0000000..be7fa7d --- /dev/null +++ b/mock_generators/mappings/README.md @@ -0,0 +1 @@ +Folder for storing mapping files if user should want to save working versions \ No newline at end of file diff --git a/mock_generators/mappings/simple_org_chart.json b/mock_generators/mappings/simple_org_chart.json new file mode 100644 index 0000000..b8279a5 Binary files /dev/null and b/mock_generators/mappings/simple_org_chart.json differ diff --git a/mock_generators/models/data_import.py b/mock_generators/models/data_import.py index dfe3b93..cbb4174 100644 --- a/mock_generators/models/data_import.py +++ b/mock_generators/models/data_import.py @@ -1,10 +1,13 @@ -from models.mapping import Mapping +# from models.mapping import Mapping +# from models.node_mapping import NodeMapping +# from models.relationship_mapping import RelationshipMapping +# from models.property_mapping import PropertyMapping +# from models.generator import GeneratorType from models.node_mapping import NodeMapping from models.relationship_mapping import RelationshipMapping from models.property_mapping import PropertyMapping -import logging -from models.generator import GeneratorType import datetime +import logging def file_schema_for_property(property: PropertyMapping)-> dict: diff --git a/mock_generators/models/generator.py b/mock_generators/models/generator.py index f65b676..c1a1a22 100644 --- a/mock_generators/models/generator.py +++ b/mock_generators/models/generator.py @@ -2,7 +2,7 @@ import logging import sys import re -import numbers +# import numbers # TODO: replace with dataclasses # from dataclasses import dataclass @@ -245,7 +245,7 @@ def generate(self, args): result = module.generate(args) return result except: - logging.error(f"Error generating data for generator {self.name}, id {self.id}: {sys.exc_info()[0]}") + logging.error(f"Error generating data for generator '{self.name}': id {self.id}: ERROR: {sys.exc_info()[0]}") return None diff --git a/mock_generators/list_utils.py b/mock_generators/models/list_utils.py similarity index 100% rename from mock_generators/list_utils.py rename to mock_generators/models/list_utils.py diff --git a/mock_generators/models/mapping.py b/mock_generators/models/mapping.py index b1d4477..b6d420a 100644 --- a/mock_generators/models/mapping.py +++ b/mock_generators/models/mapping.py @@ -1,5 +1,7 @@ +# from models.node_mapping import NodeMapping from models.node_mapping import NodeMapping + import json import logging import sys diff --git a/mock_generators/models/node_mapping.py b/mock_generators/models/node_mapping.py index b8876f2..24372b3 100644 --- a/mock_generators/models/node_mapping.py +++ b/mock_generators/models/node_mapping.py @@ -1,8 +1,12 @@ +# from models.property_mapping import PropertyMapping +# from models.generator import Generator from models.property_mapping import PropertyMapping from models.generator import Generator +from models.list_utils import clean_list + import sys +import uuid import logging -from list_utils import clean_list # TODO: Should have made these dataclasses class NodeMapping(): @@ -26,7 +30,7 @@ def __init__( position: dict, # ie: {x: 0, y: 0} caption: str, labels: list[str], - properties: dict[str, PropertyMapping], + properties: dict[str, any], count_generator: Generator, count_args: list[any], key_property: PropertyMapping): @@ -102,12 +106,12 @@ def generate_values(self) -> list[dict]: # Example return: # [ # { - # "_uid": "n1_abc", + # "_uid": "abc123", # "first_name": "John", # "last_name": "Doe" # }, # { - # "_uid": "n1_xyz", + # "_uid": "xyz123", # "first_name": "Jane", # "last_name": "Doe" # } @@ -115,14 +119,14 @@ def generate_values(self) -> list[dict]: count = 0 all_results = [] try: - count = self.count_generator.generate(self.count_args) + count = int(self.count_generator.generate(self.count_args)) except: + logging.error(f'Possibly incorrect generator assigned for count generation: {self.count_generator}') raise Exception(f"Node mapping could not generate a number of nodes to continue generation process, error: {str(sys.exc_info()[0])}") try: for _ in range(count): node_result = {} - # logging.info(f'node_mapping.py: NodeMapping.generate_values() generating values for node mapping \'{self.caption}\' with properties {self.properties}') for property_id, property in self.properties.items(): # Pass literal values if isinstance(property, PropertyMapping) == False: @@ -131,12 +135,12 @@ def generate_values(self) -> list[dict]: # Have PropertyMapping generate a value value = property.generate_value() node_result[property.name] = value - # node_result["_uid"] = f"{self.id}_{str(uuid.uuid4())[:8]}" + + # Assign a uuid all_results.append(node_result) except: raise Exception(f"Node mapping could not generate property values, error: {str(sys.exc_info()[0])}") # Store and return all_results self.generated_values = all_results - # logging.info(f'node_mapping.py: NodeMapping.generate_values() generated {len(self.generated_values)} values for node mapping {self.caption}') return self.generated_values \ No newline at end of file diff --git a/mock_generators/models/property_mapping.py b/mock_generators/models/property_mapping.py index e98354a..8840e55 100644 --- a/mock_generators/models/property_mapping.py +++ b/mock_generators/models/property_mapping.py @@ -1,15 +1,16 @@ from models.generator import Generator, GeneratorType -from list_utils import clean_list +from models.list_utils import clean_list import logging class PropertyMapping(): + # TODO: Handle literals + @staticmethod def empty(): return PropertyMapping( pid = None, name = None, - # type = None, generator = None, args = None ) @@ -18,13 +19,11 @@ def __init__( self, pid: str, name: str = None, - # type: GeneratorType = None, generator: Generator = None, # Args to pass into generator during running args: list[any] = []): self.pid = pid self.name = name - # self.type = type self.generator = generator self.args = args @@ -43,7 +42,6 @@ def to_dict(self): return { "pid": self.pid, "name": self.name, - # "type": self.type.to_string() if self.type is not None else None, "generator": self.generator.to_dict() if self.generator is not None else None, "args": clean_list(self.args) } @@ -51,8 +49,6 @@ def to_dict(self): def ready_to_generate(self): if self.name is None: return False - # if self.type is None: - # return False if self.generator is None: return False return True diff --git a/mock_generators/models/relationship_mapping.py b/mock_generators/models/relationship_mapping.py index 45c8e3b..9aec53e 100644 --- a/mock_generators/models/relationship_mapping.py +++ b/mock_generators/models/relationship_mapping.py @@ -1,12 +1,16 @@ +# from models.node_mapping import NodeMapping +# from models.property_mapping import PropertyMapping +# from models.generator import Generator + from models.node_mapping import NodeMapping from models.property_mapping import PropertyMapping from models.generator import Generator +from models.list_utils import clean_list + import logging -import random import sys from copy import deepcopy -from list_utils import clean_list class RelationshipMapping(): @@ -130,8 +134,10 @@ def generate_values( # Decide on how many of these relationships to generate count = 0 try: - count = self.count_generator.generate(self.count_args) + count = int(self.count_generator.generate(self.count_args)) except: + logging.error(f'Possibly incorrect generator assigned for count generation: {self.count_generator}') + # Generator not found or other code error raise Exception(f"Relationship mapping could not generate a number of relationships to continue generation process, error: {str(sys.exc_info()[0])}") diff --git a/mock_generators/named_generators.json b/mock_generators/named_generators.json index 4023d76..88d12a5 100644 --- a/mock_generators/named_generators.json +++ b/mock_generators/named_generators.json @@ -29,6 +29,23 @@ ], "type": "String" }, + "float": { + "args": [ + { + "default": 1.0, + "label": "Value", + "type": "Float" + } + ], + "code_url": "mock_generators/generators/float.py", + "description": "Constant Float value", + "name": "Float", + "tags": [ + "float", + "number" + ], + "type": "Float" + }, "float_from_list": { "args": [ { @@ -37,7 +54,7 @@ "type": "String" } ], - "code_url": "mock_generators/generators/111d38e0.py", + "code_url": "mock_generators/generators/float_from_list.py", "description": "Randomly selected float from a comma-seperated list of options.", "name": "Float from list", "tags": [ @@ -84,7 +101,7 @@ "type": "Integer" } ], - "code_url": "mock_generators/generators/469b37c7.py", + "code_url": "mock_generators/generators/int_range.py", "description": "Random integer from a min and max value argument. Argument values are inclusive.", "name": "Int Range", "tags": [ @@ -156,6 +173,23 @@ ], "type": "String" }, + "string": { + "args": [ + { + "default": "", + "label": "List of words (ie: alpha, brave, charlie)", + "type": "String" + } + ], + "code_url": "mock_generators/generators/literal.py", + "description": "String literal", + "name": "String", + "tags": [ + "string", + "word" + ], + "type": "String" + }, "string_from_list": { "args": [ { @@ -164,7 +198,7 @@ "type": "String" } ], - "code_url": "mock_generators/generators/5bf1fbd6.py", + "code_url": "mock_generators/generators/string_from_list.py", "description": "Randomly selected string from a comma-seperated list of options.", "name": "String from list", "tags": [ @@ -199,6 +233,20 @@ "type": "Assignment" }, "uuid": { + "args": [ + ], + "code_url": "mock_generators/generators/uuid.py", + "description": "UUID4 Generator", + "name": "UUID", + "tags": [ + "uuid", + "hash", + "unique", + "uid" + ], + "type": "String" + }, + "short_uuid": { "args": [ { "default": 37, @@ -206,14 +254,14 @@ "type": "Integer" } ], - "code_url": "mock_generators/generators/78bc0765.py", + "code_url": "mock_generators/generators/short_uuid.py", "description": "Random UUID 4 hash using Faker library. 37 Characters Max.", - "name": "UUID", + "name": "Short UUID", "tags": [ - "uuid", + "uid", "hash", "unique", - "uid" + "short" ], "type": "String" }, @@ -241,7 +289,7 @@ "type": "Datetime" } ], - "code_url": "mock_generators/generators/ab64469b.py", + "code_url": "mock_generators/generators/date.py", "description": "Generate a random date between 2 specified dates. Exclusive of days specified.", "name": "Date", "tags": [ @@ -335,7 +383,7 @@ "type": "String" } ], - "code_url": "mock_generators/generators/e56d87a3.py", + "code_url": "mock_generators/generators/int_from_list.py", "description": "Randomly selected int from a comma-seperated list of options. If no list provided, will return 0", "name": "Int from list", "tags": [ @@ -368,7 +416,7 @@ "type": "Integer" } ], - "code_url": "mock_generators/generators/e8cff8c1.py", + "code_url": "mock_generators/generators/float_range.py", "description": "Random float between a range. Inclusive.", "name": "Float", "tags": [ @@ -390,7 +438,7 @@ "type": "Integer" } ], - "code_url": "mock_generators/generators/ecdff22c.py", + "code_url": "mock_generators/generators/int.py", "description": "Constant integer value", "name": "Int", "tags": [ @@ -415,7 +463,7 @@ "type": "Integer" } ], - "code_url": "mock_generators/generators/loremtext_words.py", + "code_url": "mock_generators/generators/lorem_words.py", "description": "String generator using the lorem-text package", "name": "Words", "tags": [ @@ -439,7 +487,7 @@ "type": "Integer" } ], - "code_url": "mock_generators/generators/loremtext_sentence.py", + "code_url": "mock_generators/generators/lorem_sentences.py", "description": "String generator using the lorem-text package", "name": "Sentences", "tags": [ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..20c0009 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[tool.pytest.ini_options] +log_cli = true +log_cli_level = "INFO" +log_cli_format = "%(asctime)s %(levelname)8s %(filename)s: %(funcName)20s():%(lineno)s %(message)s" +log_cli_date_format = "%Y-%m-%d %H:%M:%S" \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6e4a333 --- /dev/null +++ b/tests/README.md @@ -0,0 +1 @@ +# Why didn't I just build a test framework that reads from JSON files with inputs and expected outputs? \ No newline at end of file diff --git a/tests/test_generate_mappings.py b/tests/test_generate_mappings.py index 7a3b427..c685710 100644 --- a/tests/test_generate_mappings.py +++ b/tests/test_generate_mappings.py @@ -1,63 +1,16 @@ +import os import pytest -# from mock_generators import __version__ - -from mock_generators.models.generator import Generator, GeneratorType, GeneratorArg - -from mock_generators.models.node_mapping import NodeMapping - -from mock_generators.generate_mapping import generator_for_raw_property, mapping_from_json, propertymappings_for_raw_properties, node_mappings_from +from mock_generators.models.generator import Generator, GeneratorType import json -# For pytest to properly find modules, add following to pyproject.toml: -# [tool.pytest.ini_options] -# pythonpath = [ -# ".", "mock_generators" -# ] - -int_args_test = GeneratorArg( - type=GeneratorType.INT, - label="test_arg", - default=1, - ) - -int_generator_test = Generator( - id="test_int", - name="test_int", - type=GeneratorType.INT, - description="test", - args=[int_args_test], - code_url="mock_generators/generators/ecdff22c.py", - tags=["int, integer, number"] - ) - -float_args_test = [ - GeneratorArg( - type=GeneratorType.FLOAT, - label="min", - default=1.0, - ), - GeneratorArg( - type=GeneratorType.FLOAT, - label="max", - default=2.0, - ), - ] - -float_generator_test = Generator( - id="test_float", - name = "test_float", - type=GeneratorType.FLOAT, - description="test", - args=float_args_test, - code_url="mock_generators/generators/e8cff8c1.py", - tags=["float", "number"] - ) - -test_generators = { - "test_int" : int_generator_test, - "test_float" : float_generator_test -} +import logging +# test_generators = load_generators("mock_generators/named_generators.json") + +# TODO: Test propertymappings_for_raw_properties +# TODO: Test node_mappings_from +# TODO: Test relationshipmappings_from +# TODO: Test mapping_from_json class TestClass: # def test_version(self): @@ -101,49 +54,67 @@ def test_jsonification(self): except Exception as e: print(f'Exception: {e}') assert False + + # def test_failed_generator_for_raw_property(self): + # try: + # generator, args = generator_for_raw_property("{\'test_doesnt_exist\':[1]}", test_generators) + # assert generator is None + # assert args is None + # except Exception as e: + # print(f'Exception: {e}') + + + # def test_propertymappings_for_raw_properties_smoke(self): + # raw_props = { + # "alpha": "{\"test_int\":[1]}", + # "bravo": "{\"test_float\":[1.0, 2.0]}" + # } + # mappings = propertymappings_for_raw_properties( + # raw_properties=raw_props, + # generators= test_generators) + # assert len(mappings) == 2 + + # def test_node_mappings_from_literals(self): + # nodes = [ + # { + # "id": "n1", + # "position": { + # "x": 284.5, + # "y": -204 + # }, + # "caption": "Company", + # "labels": [], + # "properties": { + # "string": "name", + # "bool": "bool", + # "int": "int", + # "float": "float", + # "datetime": "2020-01-01T00:00:00Z" + # }, + # "style": {} + # } + # ] + + # def test_propertymappings_for_raw_properties_literals(self): + # raw_props = { + # "string": "test", + # "bool": True, + # "int": 1, + # "float": 1.0, + # "datetime": "2020-01-01T00:00:00Z", + # } + # mappings = propertymappings_for_raw_properties( + # raw_properties=raw_props, + # generators= test_generators) + # assert len(mappings) == 5 + # assert mappings["string"] == "test" + # assert mappings["bool"] == True + # assert mappings["int"] == 1 + # assert mappings["float"] == 1.0 + # assert mappings["datetime"] == "2020-01-01T00:00:00Z" + - def test_generator_for_raw_property(self): - print(f'int_generator_test: {int_generator_test}') - print(f'int_args_test: {int_args_test}') - print(f'test_generators: {test_generators}') - try: - test_string = "{\"test_int\":[1]}" - generator, args = generator_for_raw_property(test_string, test_generators) - except Exception as e: - print(f'Exception: {e}') - print(f'generator: {generator}, args: {args}') - # if status_message is not None: - # print(f'status: {status_message}') - assert int_generator_test is not None - assert int_args_test is not None - assert generator == int_generator_test - assert args == [1] - # Test generator returned creates acceptable value - value = generator.generate(args) - assert value == 1 - - def test_failed_generator_for_raw_property(self): - try: - generator, args = generator_for_raw_property("{\'test_doesnt_exist\':[1]}", test_generators) - except Exception as e: - print(f'Exception: {e}') - assert generator is None - assert args is None - - def test_propertymappings_for_raw_properties(self): - mappings = propertymappings_for_raw_properties( - raw_properties={ - "alpha": "{\'test_int\':[1]", - "bravo": "{\'test_float\':[1.0, 2.0]" - }, - generators= test_generators) - print(f'mappings: {mappings}') - assert len(mappings) == 2 - assert mappings['alpha'].generator == self.int_generator_test() - assert mappings['alpha'].args == self.int_args_test() - assert mappings['bravo'].generator == self.float_generator_test() - assert mappings['bravo'].args == self.float_args_test() # def test_node_mappings_from(self): # nodes = node_mappings_from( diff --git a/tests/test_generate_values.py b/tests/test_generate_values.py new file mode 100644 index 0000000..ca297d8 --- /dev/null +++ b/tests/test_generate_values.py @@ -0,0 +1,318 @@ +import pytest +from mock_generators.config import load_generators +from mock_generators.logic.generate_values import literal_generator_from_value, actual_generator_for_raw_property, generator_for_raw_property, keyword_generator_for_raw_property + + +test_generators = load_generators("mock_generators/named_generators.json") + +# TODO: Probably not the most ideal way to test these + +class TestActualGenerators: + + def test_non_conforming(self): + try: + # String literal + test_string = "invalid" + generator, args = actual_generator_for_raw_property(test_string, test_generators) + assert generator == None + assert args == None + except Exception as e: + assert False, f'Exception: {e}' + + + try: + # Empty + test_string = "{}" + generator, args = actual_generator_for_raw_property(test_string, test_generators) + assert generator == None + assert args == None + except Exception as e: + assert False, f'Exception: {e}' + + + try: + # Number + test_string = "6" + generator, args = actual_generator_for_raw_property(test_string, test_generators) + assert generator == None + assert args == None + except Exception as e: + assert False, f'Exception: {e}' + + + try: + # Empty + test_string = "" + generator, args = actual_generator_for_raw_property(test_string, test_generators) + assert generator == None + assert args == None + except Exception as e: + assert False, f'Exception: {e}' + + + def test_integer(self): + try: + test_string = "{\"int\": [1]}" + generator, args = actual_generator_for_raw_property(test_string, test_generators) + assert args == [1] + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value == 1 + except Exception as e: + assert False, f'Exception: {e}' + + + def test_integer_list_single(self): + try: + test_string = "{\"int_from_list\":[\"1\"]}" + generator, args = actual_generator_for_raw_property(test_string, test_generators) + assert args == ["1"] + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value == 1 + except Exception as e: + assert False, f'Exception: {e}' + + + def test_integer_list_multi(self): + try: + test_string = "{\"int_from_list\": [\"1,2,3\"]}" + generator, args = actual_generator_for_raw_property(test_string, test_generators) + assert args == ["1,2,3"] + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value in [1,2,3] + except Exception as e: + assert False, f'Exception: {e}' + + +class TestLiteralGenerators: + def test_integer(self): + try: + test_string = "1" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value == 1 + except Exception as e: + assert False, f'Exception: {e}' + + + def test_float(self): + try: + test_string = "1.0" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value == 1.0 + except Exception as e: + assert False, f'Exception: {e}' + + + def test_int_range(self): + try: + test_string = "1-3" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value in [1,2,3] + except Exception as e: + assert False, f'Exception: {e}' + + + def test_float_range(self): + try: + test_string = "1.0-2" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value <= 2.0 + assert value >= 1.0 + except Exception as e: + assert False, f'Exception: {e}' + + + def test_int_list(self): + try: + test_string = "[1,2,3]" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value in [1,2,3] + except Exception as e: + assert False, f'Exception: {e}' + + + def test_float_list(self): + try: + test_string = "[1.0, 2, 3.0]" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value in [1.0, 2.0, 3.0], f'generator: {generator}, args: {args}, value: {value}' + except Exception as e: + assert False, f'Exception: {e}' + + + def test_string(self): + try: + test_string = "A string value" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value == "A string value" + except Exception as e: + assert False, f'Exception: {e}' + + + def test_string_from_list(self): + try: + test_string = "[Chicken, Peas, Carrots]" + # This should be equivalent to the integer generator with arg of [1] + generator, args = literal_generator_from_value(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value in ["Chicken", "Peas", "Carrots"] + except Exception as e: + assert False, f'Exception: {e}' + + +class TestGeneratorForProperties: + def test_failed_generator_for_raw_property(self): + # Will default to a string literal + try: + generator, args = generator_for_raw_property("{\'test_doesnt_exist\':[1]}", test_generators) + value = generator.generate(args) + assert value == "{\'test_doesnt_exist\':[1]}" + except Exception as e: + assert False, f'Exception: {e}' + + def test_string_routing(self): + try: + test_string = "literal string" + # This should be equivalent to the integer generator with arg of [1] + generator, args = generator_for_raw_property(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value == "literal string" + except Exception as e: + assert False, f'Exception: {e}' + + + try: + # List of strings + test_string = "[Chicken, Peas, Carrots]" + # This should be equivalent to the integer generator with arg of [1] + generator, args = generator_for_raw_property(test_string, test_generators) + # Test generator returned creates acceptable value + value = generator.generate(args) + assert value in ["Chicken", "Peas", "Carrots"] + except Exception as e: + assert False, f'Exception: {e}' + + + +class TestKeywordGeneratorForProperties: + def test_no_keyword_found(self): + try: + generator, args = keyword_generator_for_raw_property("NOT A KEYWORD", test_generators) + assert generator is None + assert args is None + except Exception as e: + assert False, f'Exception: {e}' + + def test_bool_keywords(self): + try: + generator, args = keyword_generator_for_raw_property("bool", test_generators) + value = generator.generate(args) + assert value in [True, False] + except Exception as e: + assert False, f'Exception: {e}' + + try: + generator, args = keyword_generator_for_raw_property("boolean", test_generators) + value = generator.generate(args) + assert value in [True, False] + except Exception as e: + assert False, f'Exception: {e}' + + try: + generator, args = keyword_generator_for_raw_property("Boolean", test_generators) + value = generator.generate(args) + assert value in [True, False] + except Exception as e: + assert False, f'Exception: {e}' + + def test_int_keywords(self): + try: + generator, args = keyword_generator_for_raw_property("int", test_generators) + value = generator.generate(args) + assert value <= 100 + assert value >= 1 + except Exception as e: + assert False, f'Exception: {e}' + + try: + generator, args = keyword_generator_for_raw_property("integer", test_generators) + value = generator.generate(args) + assert value <= 100 + assert value >= 1 + except Exception as e: + assert False, f'Exception: {e}' + + try: + generator, args = keyword_generator_for_raw_property("Integer", test_generators) + value = generator.generate(args) + assert value <= 100 + assert value >= 1 + except Exception as e: + assert False, f'Exception: {e}' + + def test_float_keywords(self): + try: + generator, args = keyword_generator_for_raw_property("float", test_generators) + value = generator.generate(args) + assert value <= 100.0 + assert value >= 1.0 + except Exception as e: + assert False, f'Exception: {e}' + + try: + generator, args = keyword_generator_for_raw_property("Float", test_generators) + value = generator.generate(args) + assert value <= 100.0 + assert value >= 1.0 + except Exception as e: + assert False, f'Exception: {e}' + + def test_date_keywords(self): + from datetime import datetime + + try: + generator, args = keyword_generator_for_raw_property("date", test_generators) + value = generator.generate(args) + lower_bound = datetime.fromisoformat('1970-01-01T00:00:00') + upper_bound = datetime.fromisoformat('2022-11-24T00:00:00') + check_date = datetime.fromisoformat(value) + assert lower_bound <= check_date <= upper_bound + except Exception as e: + assert False, f'Exception: {e}' + + + try: + generator, args = keyword_generator_for_raw_property("Datetime", test_generators) + value = generator.generate(args) + lower_bound = datetime.fromisoformat('1970-01-01T00:00:00') + upper_bound = datetime.fromisoformat('2022-11-24T00:00:00') + check_date = datetime.fromisoformat(value) + print(f'check_date: {check_date}') + assert lower_bound <= check_date <= upper_bound + except Exception as e: + assert False, f'Exception: {e}' diff --git a/tests/test_generators.py b/tests/test_generators.py new file mode 100644 index 0000000..a4d0f93 --- /dev/null +++ b/tests/test_generators.py @@ -0,0 +1,42 @@ +import os +import pytest +from mock_generators.config import load_generators +from mock_generators.logic.generate_values import actual_generator_for_raw_property +from datetime import datetime +test_generators = load_generators("mock_generators/named_generators.json") + +class TestDateGenerator: + def test_date_generator(self): + try: + test_string = '{"date": ["1970-01-01", "2022-11-24"]}' + generator, args = actual_generator_for_raw_property(test_string, test_generators) + value = generator.generate(args) + + lower_bound = datetime.fromisoformat('1970-01-01T00:00:00') + upper_bound = datetime.fromisoformat('2022-11-24T00:00:00') + + # define a datetime object to check + check_date = datetime.fromisoformat(value) + + # check if the check_date is within the bounds + assert lower_bound <= check_date <= upper_bound + except Exception as e: + print(f'Exception: {e}') + assert False + + try: + test_string = '{"date": ["1970-01-01", "1970-01-01"]}' + generator, args = actual_generator_for_raw_property(test_string, test_generators) + value = generator.generate(args) + + lower_bound = datetime.fromisoformat('1970-01-01T00:00:00') + upper_bound = datetime.fromisoformat('1970-01-01T00:00:00') + + # define a datetime object to check + check_date = datetime.fromisoformat(value) + + # check if the check_date is within the bounds + assert lower_bound <= check_date <= upper_bound + except Exception as e: + print(f'Exception: {e}') + assert False \ No newline at end of file