{"id":13343053,"url":"https://github.com/ifak/fences","last_synced_at":"2025-03-12T04:32:03.922Z","repository":{"id":149229888,"uuid":"599800855","full_name":"ifak/fences","owner":"ifak","description":"Generate samples for various schemas like json schema and regex","archived":false,"fork":false,"pushed_at":"2024-04-13T15:50:43.000Z","size":214,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-04-14T05:02:02.801Z","etag":null,"topics":["grammar","json","regex","schema","testing","verification","xml"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ifak.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-09T22:51:25.000Z","updated_at":"2024-07-14T22:30:15.697Z","dependencies_parsed_at":null,"dependency_job_id":"a7d48318-af65-4e02-b6d6-f5ad6a4bfdea","html_url":"https://github.com/ifak/fences","commit_stats":{"total_commits":36,"total_committers":2,"mean_commits":18.0,"dds":0.02777777777777779,"last_synced_commit":"a4a9ed7ae865f29113f675a17a761de34ed09593"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifak%2Ffences","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifak%2Ffences/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifak%2Ffences/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ifak%2Ffences/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ifak","download_url":"https://codeload.github.com/ifak/fences/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243158977,"owners_count":20245668,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["grammar","json","regex","schema","testing","verification","xml"],"created_at":"2024-07-29T19:30:36.815Z","updated_at":"2025-03-12T04:32:03.571Z","avatar_url":"https://github.com/ifak.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fences\n[![Tests](https://github.com/ifak/fences/actions/workflows/check.yml/badge.svg)](https://github.com/ifak/fences/actions/workflows/check.yml)\n\nFences is a python tool which lets you create test data based on schemas.\n\nFor this, it generates a set of *valid samples* which fullfil your schema.\nAdditionally, it generates a set of *invalid samples* which intentionally violate your schema.\nYou can then feed these samples into your software to test.\nIf your software is implemented correctly, it must accept all valid samples and reject all invalid ones.\n\nUnlike other similar tools, fences generate samples systematically instead of randomly.\nThis way, the valid / invalid samples systematically cover all boundaries of your input schema (like placing *fences*, hence the name).\n\n## Installation\n\nUse pip to install Fences:\n\n```\npython -m pip install fences\n```\n\nFences is a self contained library without any external dependencies.\nIt uses [Lark](https://github.com/lark-parser/lark) for regex parsing, but in the standalone version where a python file is generated from the grammar beforehand (Mozilla Public License, v. 2.0).\n\n## Usage\n\n### Regular Expressions\n\nGenerate samples for regular expressions:\n\n```python\nfrom fences import parse_regex\n\ngraph = parse_regex(\"a?(c+)b{3,7}\")\n\nfor i in graph.generate_paths():\n    sample = graph.execute(i.path)\n    print(\"Valid:\" if i.is_valid else \"Invalid:\")\n    print(sample)\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eOutput\u003c/summary\u003e\n\n```\nValid:\ncbbb\nValid:\nacccbbbbbbb\n```\n\u003c/details\u003e\n\n### JSON Schema\n\nGenerate samples for json schema:\n\n```python\nfrom fences import parse_json_schema\nimport json\n\ngraph = parse_json_schema({\n    'properties': {\n        'foo': {\n            'type': 'string'\n        },\n        'bar': {\n            'type': 'boolean'\n        }\n    }\n})\n\nfor i in graph.generate_paths():\n    sample = graph.execute(i.path)\n    print(\"Valid:\" if i.is_valid else \"Invalid:\")\n    print(json.dumps(sample, indent=4))\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eOutput\u003c/summary\u003e\n\n```json\nValid:\n{\n    \"foo\": \"\",\n    \"bar\": true\n}\nValid:\n{}\nValid:\n{\n    \"foo\": \"\",\n    \"bar\": false\n}\nValid:\n\"\"\nValid:\n[\n    \"string\"\n]\nValid:\n[\n    42\n]\nValid:\n[\n    null\n]\nValid:\n[\n    true\n]\nValid:\n[\n    false\n]\nValid:\n[\n    {}\n]\nValid:\n[\n    []\n]\nValid:\ntrue\nValid:\nfalse\nValid:\n0\nValid:\nnull\nInvalid:\n{\n    \"foo\": 42\n}\nInvalid:\n{\n    \"foo\": null\n}\nInvalid:\n{\n    \"foo\": true,\n    \"bar\": true\n}\nInvalid:\n{\n    \"foo\": false\n}\nInvalid:\n{\n    \"foo\": {},\n    \"bar\": true\n}\nInvalid:\n{\n    \"foo\": []\n}\nInvalid:\n{\n    \"bar\": \"string\"\n}\nInvalid:\n{\n    \"bar\": 42\n}\nInvalid:\n{\n    \"bar\": null\n}\nInvalid:\n{\n    \"bar\": {}\n}\nInvalid:\n{\n    \"bar\": []\n}\n```\n\u003c/details\u003e\n\n### XML Schema\n\nGenerate samples for XML schema:\n\n```python\nfrom fences import parse_xml_schema\nfrom xml.etree import ElementTree\nfrom xml.dom import minidom\n\net = ElementTree.fromstring(\"\"\"\u003c?xml version=\"1.0\" encoding=\"UTF-8\" ?\u003e\n    \u003cxs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\u003e\n        \u003cxs:element name = 'class'\u003e\n            \u003cxs:complexType\u003e\n                \u003cxs:sequence\u003e\n                    \u003cxs:element name = 'student' type = 'StudentType' minOccurs = '0' maxOccurs = 'unbounded' /\u003e\n                \u003c/xs:sequence\u003e\n            \u003c/xs:complexType\u003e\n        \u003c/xs:element\u003e\n        \u003cxs:complexType name = \"StudentType\"\u003e\n            \u003cxs:sequence\u003e\n                \u003cxs:element name = \"firstname\" type = \"xs:string\"/\u003e\n                \u003cxs:element name = \"lastname\" type = \"xs:string\"/\u003e\n                \u003cxs:element name = \"nickname\" type = \"xs:string\"/\u003e\n                \u003cxs:element name = \"marks\" type = \"xs:positiveInteger\"/\u003e\n            \u003c/xs:sequence\u003e\n            \u003cxs:attribute name = 'rollno' type = 'xs:positiveInteger'/\u003e\n        \u003c/xs:complexType\u003e\n    \u003c/xs:schema\u003e\"\"\")\n\ngraph = parse_xml_schema(et)\nfor i in graph.generate_paths():\n    sample = graph.execute(i.path)\n    s = ElementTree.tostring(sample.getroot())\n    print(\"Valid:\" if i.is_valid else \"Invalid:\")\n    print(minidom.parseString(s).toprettyxml(indent=\"   \"))\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eOutput\u003c/summary\u003e\n\n```xml\nValid:\n\u003c?xml version=\"1.0\" ?\u003e\n\u003cclass/\u003e\n\nValid:\n\u003c?xml version=\"1.0\" ?\u003e\n\u003cclass\u003e\n   \u003cstudent\u003e\n      \u003cfirstname\u003efoo\u003c/firstname\u003e\n      \u003clastname\u003efoo\u003c/lastname\u003e\n      \u003cnickname\u003efoo\u003c/nickname\u003e\n      \u003cmarks\u003e780\u003c/marks\u003e\n   \u003c/student\u003e\n\u003c/class\u003e\n\nValid:\n\u003c?xml version=\"1.0\" ?\u003e\n\u003cclass\u003e\n   \u003cstudent rollno=\"533\"\u003e\n      \u003cfirstname\u003ex\u003c/firstname\u003e\n      \u003clastname\u003ex\u003c/lastname\u003e\n      \u003cnickname\u003ex\u003c/nickname\u003e\n      \u003cmarks\u003e780\u003c/marks\u003e\n   \u003c/student\u003e\n\u003c/class\u003e\n\nInvalid:\n\u003c?xml version=\"1.0\" ?\u003e\n\u003cclass\u003e\n   \u003cstudent\u003e\n      \u003cfirstname\u003efoo\u003c/firstname\u003e\n      \u003clastname\u003efoo\u003c/lastname\u003e\n      \u003cnickname\u003efoo\u003c/nickname\u003e\n      \u003cmarks\u003e-10\u003c/marks\u003e\n   \u003c/student\u003e\n\u003c/class\u003e\n\nInvalid:\n\u003c?xml version=\"1.0\" ?\u003e\n\u003cclass\u003e\n   \u003cstudent rollno=\"533\"\u003e\n      \u003cfirstname\u003ex\u003c/firstname\u003e\n      \u003clastname\u003ex\u003c/lastname\u003e\n      \u003cnickname\u003ex\u003c/nickname\u003e\n      \u003cmarks\u003efoo\u003c/marks\u003e\n   \u003c/student\u003e\n\u003c/class\u003e\n\nInvalid:\n\u003c?xml version=\"1.0\" ?\u003e\n\u003cclass\u003e\n   \u003cstudent rollno=\"-10\"\u003e\n      \u003cfirstname\u003efoo\u003c/firstname\u003e\n      \u003clastname\u003efoo\u003c/lastname\u003e\n      \u003cnickname\u003efoo\u003c/nickname\u003e\n      \u003cmarks\u003e780\u003c/marks\u003e\n   \u003c/student\u003e\n\u003c/class\u003e\n\nInvalid:\n\u003c?xml version=\"1.0\" ?\u003e\n\u003cclass\u003e\n   \u003cstudent rollno=\"foo\"\u003e\n      \u003cfirstname\u003ex\u003c/firstname\u003e\n      \u003clastname\u003ex\u003c/lastname\u003e\n      \u003cnickname\u003ex\u003c/nickname\u003e\n      \u003cmarks\u003e780\u003c/marks\u003e\n   \u003c/student\u003e\n\u003c/class\u003e\n```\n\n\u003c/details\u003e\n\n### Grammar\n\nGenerate samples for a grammar:\n\n```python\nfrom fences.grammar.types import NonTerminal, CharacterRange\nfrom fences import parse_grammar\n\nnumber = NonTerminal(\"number\")\ninteger = NonTerminal(\"integer\")\nfraction = NonTerminal(\"fraction\")\nexponent = NonTerminal(\"exponent\")\ndigit = NonTerminal(\"digit\")\ndigits = NonTerminal(\"digits\")\none_to_nine = NonTerminal(\"one_to_nine\")\nsign = NonTerminal(\"sign\")\n\ngrammar = {\n    number:      integer + fraction + exponent,\n    integer:     digit\n                 | one_to_nine + digits\n                 | '-' + digit\n                 | '-' + one_to_nine + digits,\n    digit:       '0'\n                 | one_to_nine,\n    digits:      digit*(1, None),\n    one_to_nine: CharacterRange('1', '9'),\n    fraction:    \"\"\n                 | \".\" + digits,\n    exponent:    \"\"\n                 | 'E' + sign + digits\n                 | \"e\" + sign + digits,\n    sign:        [\"\", \"+\", \"-\"]\n}\n\ngraph = parse_grammar(grammar, number)\nfor i in graph.generate_paths():\n    sample = graph.execute(i.path)\n    print(sample)\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eOutput\u003c/summary\u003e\n\n```\n0\n91.0901E0901\n-0e+9\n-10901.0\n9E-0109\n```\n\n\u003c/details\u003e\n\n### OpenAPI (Swagger)\n\nYou can use Fences to parse an OpenAPI specification and generate a set of sample requests:\n\n```python\nfrom fences.open_api.generate import generate_all, SampleCache\nfrom fences.open_api.open_api import OpenApi\n\ndescription = {\n    'info': {\n        'title': 'Video API'\n    },\n    'paths': {\n        '/videos': {\n            'get': {\n                'operationId': 'listVideos',\n                'parameters': [{\n                    'name': 'type',\n                    'in': 'query',\n                    'schema': {\n                        'enum': ['public', 'private']\n                    }\n                }, {\n                    'name': 'title',\n                    'in': 'query',\n                    'schema': {\n                        'type': 'string',\n                        'minLength': 3\n                    }\n                }],\n                'responses': {}\n            }\n        },\n        '/videos/{videoId}': {\n            'patch': {\n                'operationId': 'updateVideo',\n                'parameters': [\n                    {\n                        'name': 'videoId',\n                        'in': 'path',\n                        'schema': {\n                            'type': 'number'\n                        }\n                    }\n                ],\n                'requestBody': {\n                    'content': {\n                        'application/json': {\n                            'schema': {\n                                'type': 'object',\n                                'properties': {\n                                    'title': {\n                                        'type': 'string',\n                                        'minLength': 10\n                                    }\n                                },\n                                'required': ['title']\n                            }\n                        }\n                    }\n                },\n                'responses': {}\n            }\n        }\n    }\n}\n\nopen_api = OpenApi.from_dict(description)\nsample_cache = SampleCache()\nfor operation in open_api.operations.values():\n    graph = generate_all(operation, sample_cache)\n    for i in graph.generate_paths():\n        request = graph.execute(i.path)\n        request.dump()\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eOutput\u003c/summary\u003e\n\n```\nGET /videos\nGET /videos?type=public\u0026title=xxx\nGET /videos?type=private\nGET /videos?type=%23%23%23%23%23%23%23%23\u0026title=xxx\nPATCH /videos/0\n  BODY: {\"title\": \"xxxxxxxxxx\"}\nPATCH /videos/0\nPATCH /videos/0\n  BODY: {\"title\": 42}\nPATCH /videos/0\n  BODY: {\"title\": null}\nPATCH /videos/0\n  BODY: {\"title\": true}\nPATCH /videos/0\n  BODY: {\"title\": false}\nPATCH /videos/0\n  BODY: {\"title\": {}}\nPATCH /videos/0\n  BODY: {\"title\": []}\nPATCH /videos/0\n  BODY: {}\nPATCH /videos/0\n  BODY: \"string\"\nPATCH /videos/0\n  BODY: 42\nPATCH /videos/0\nPATCH /videos/0\n  BODY: true\nPATCH /videos/0\n  BODY: false\nPATCH /videos/0\n  BODY: []\n```\n\u003c/details\u003e\n\nYou can execute the generated tests using the `request.execute()` method.\nPlease note, that you need to install the `requests` library for this.\n\n## Real-World Examples\n\nFind some real-world examples in the `examples` folder.\n\n## Limitations\n\nGeneral:\n\nFences does not check if your schema is syntactically correct.\nFences is designed to be as permissive as possible when parsing a schema but will complain if there is an aspect it does not understand.\n\nFor XML:\n\nPython's default XML implementation `xml.etree.ElementTree` has a very poor support for namespaces (https://docs.python.org/3/library/xml.etree.elementtree.html#parsing-xml-with-namespaces).\nThis might lead to problems when using the `targetNamespace` attribute in your XML schema.\n\nFor Grammars:\n\nFences currently does not generate invalid samples for grammars.\n\nFor OpenAPI:\n\nThe test cases generated by Fences are purely syntactic. They do not check for semantics, e.g. if retrieving a deleted resource returns 404.\n ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fifak%2Ffences","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fifak%2Ffences","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fifak%2Ffences/lists"}