Skip to content

Commit

Permalink
v0.4.0 Release (#63)
Browse files Browse the repository at this point in the history
* Added import statements to the Yamlator schema syntax that support importing one or more types
* Added namespaces to the import statements with the as keyword
* Improvements to the loading of rulesets to allow for schema files to be less restrictive in the order resources are defined.
* Improvements to the grammar file to include new terminals and remove duplicate constructs
* Updated package build to 0.4.0 in the setup.py
  • Loading branch information
ryan95f authored Mar 30, 2023
2 parents 6c83f1b + a253325 commit 4cf068a
Show file tree
Hide file tree
Showing 43 changed files with 2,023 additions and 215 deletions.
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@
* Added top level validation to Yamlator to support data structures that may not be an object or a map
* Minor docstring and structure improvements in the `tests/` module
* Improvements to the codebase docstrings

## v4.0.0 (30th March 2023)

* Added import statements to the Yamlator schema syntax that support importing one or more types
* Added namespaces to the import statements with the `as` keyword
* Improvements to the loading of rulesets to allow for schema files to be less restrictive in the order resources are defined.
* Improvements to the grammar file to include new terminals and remove duplicate constructs
53 changes: 53 additions & 0 deletions docs/schema_components.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Below are the various components that can be used to construct a schema with Yam
* [Ruleset Type](#ruleset-type)
* [Enum Type](#enum-type)
* [Union Type](#union-type)
* [Importing schemas](#importing-schemas)
* [Schema import paths](#schema-import-paths)

## Schema

Expand Down Expand Up @@ -423,3 +425,54 @@ schema {
items list(Item)
}
```

## Importing Schemas

As of version `0.4.0`, Yamlator now supports importing rulesets and enums from other schema files. A single import statement can import one or more resources. Imports can also be given an optional namespace to support importing multiple resources with the same name from different schemas. For example, below is an import statement structure without a namespace:

```text
import <COMMA SEPARATED LIST OF RESOURCES> from <RESOURCE PATH>
```

Or an import statement with a namespace:

```text
import <COMMA SEPARATED LIST OF RESOURCES> from <RESOURCE PATH> as <NAMESPACE>
```

For example, given the schema called `main.ys`, which is importing resources from two different schemas:

```text
import Api from "../web/apis.ys"
import Status, ProjectDetails from "common.ys" as core
schema {
project Project
}
strict ruleset Project {
status core.Status
apis list(Api)
details core.ProjectDetails
}
```

Any imported resources that use a namespace must have the namespace and the resource type specified in the rule type. For example, in the case of importing `ProjectDetails`, which is coming from the `core` namespace, you can see that the rule type is `core.ProjectDetails`. If the namespace is omitted for an imported resource that uses a namespace, a unknown type error will be shown and Yamlator will exit with a non-zero status code.

__NOTE__: Importing schema blocks and using wildcards (typically seen as `*`) to import every resource are not supported in Yamlator.

### Schema import paths

When importing a schema, the path is the location of the schema relative to schema that is using its resources. For example, given the schema above, it would exist in the following file structure:

```text
web/
apis.ys
main/
common.ys
main.ys
```

The `apis` will be fetched from the `web` directory and `common.ys` is located in the same directory location as the `main.ys` file. An full example of importing schemas can be found in [example folder](../example/imports/).

__NOTE__: If an import cycle is detected, Yamlator will exit with a non-zero status code.
17 changes: 17 additions & 0 deletions example/imports/common.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Project from "../strict_mode/strict.ys"

enum Values {
TEST = 1
}

enum Status {
SUCCESS = 0
ERR = 1
FAILURE = 2
WARNING = 3
}

ruleset ProjectDetails {
author str
created str
}
3 changes: 3 additions & 0 deletions example/imports/import.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
project:
version: 1
name: test
12 changes: 12 additions & 0 deletions example/imports/import.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Values, Status, ProjectDetails from "common.ys" as core

schema {
project Project
value core.Values
}

strict ruleset Project {
status core.Status
apis list(str)
details core.ProjectDetails
}
2 changes: 1 addition & 1 deletion example/lists/lists.ys
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ruleset User {
strict ruleset Project {
version int required
name str required
users list(User) optional
users list(User)
}

strict schema {
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import setuptools

VERSION = '0.3.1'
VERSION = '0.4.0'
PACKAGE_NAME = 'yamlator'
DESCRIPTION = 'Yamlator is a CLI tool that allows a YAML file to be validated using a lightweight schema language' # nopep8

Expand Down Expand Up @@ -45,7 +45,8 @@ def create_long_description():
'yamlator',
'yamlator.validators',
'yamlator.cmd',
'yamlator.cmd.outputs'
'yamlator.cmd.outputs',
'yamlator.parser',
]),
install_requires=[
'lark>=1.0.0',
Expand Down
26 changes: 26 additions & 0 deletions tests/cmd/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Contains constants for the test files paths"""

_BASE_INVALID_PATH = './tests/files/invalid_files'

INVALID_ENUM_NAME_SCHEMA = f'{_BASE_INVALID_PATH}/invalid_enum_name.ys'
INVALID_RULESET_NAME_SCHEMA = f'{_BASE_INVALID_PATH}/invalid_ruleset_name.ys'
INVALID_SYNTAX_SCHEMA = f'{_BASE_INVALID_PATH}/invalid_syntax.ys'
MISSING_RULESET_DEF_SCHEMA = f'{_BASE_INVALID_PATH}/missing_defined_ruleset.ys'
NESTED_UNION_SCHEMA = f'{_BASE_INVALID_PATH}/nested_union.ys'
MISSING_RULESET_RULES_SCHEMA = f'{_BASE_INVALID_PATH}/ruleset_missing_rules.ys'
MISSING_SCHEMA_RULES_SCHEMA = f'{_BASE_INVALID_PATH}/schema_missing_rules.ys'
SELF_CYCLE_SCHEMA = f'{_BASE_INVALID_PATH}/cycles/self_cycle.ys'
INVALID_YAML_DATA = f'{_BASE_INVALID_PATH}/invalid.yaml'

_BASE_VALID_PATH = './tests/files/valid'
VALID_YAML_DATA = f'{_BASE_VALID_PATH}/valid.yaml'
VALID_SCHEMA = f'{_BASE_VALID_PATH}/valid.ys'
VALID_KEYLESS_DIRECTIVE_SCHEMA = f'{_BASE_VALID_PATH}/keyless_directive.ys'
VALID_KEYLESS_RULES_SCHEMA = f'{_BASE_VALID_PATH}/keyless_and_standard_rules.ys'

# These files don't exists and are used to force specific errors in the tests
NONE_PATH = None
EMPTY_PATH = ''
NOT_FOUND_SCHEMA = 'not_found.ys'
NOT_FOUND_YAML_DATA = 'not_found.yaml'
INVALID_SCHEMA_EXTENSION = './tests/files/hello.ruleset'
46 changes: 25 additions & 21 deletions tests/cmd/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
from yamlator.cmd import DisplayMethod
from yamlator.cmd.outputs import SuccessCode

HELLO_YAML_FILE_PATH = './tests/files/valid/valid.yaml'
HELLO_RULESET_FILE_PATH = './tests/files/valid/valid.ys'
INVALID_HELLO_YAML_FILE_PATH = './tests/files/valid/invalid.yaml'
from tests.cmd import constants


ValidateArgs = namedtuple('ValidateArgs', ['file', 'ruleset_schema', 'output'])

Expand All @@ -31,48 +30,53 @@ class TestMain(unittest.TestCase):

@parameterized.expand([
('with_yaml_matching_ruleset', ValidateArgs(
HELLO_YAML_FILE_PATH,
HELLO_RULESET_FILE_PATH,
constants.VALID_YAML_DATA,
constants.VALID_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.SUCCESS),
('with_yaml_containing_ruleset_violations', ValidateArgs(
INVALID_HELLO_YAML_FILE_PATH,
HELLO_RULESET_FILE_PATH,
constants.INVALID_YAML_DATA,
constants.VALID_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_ruleset_file_not_found', ValidateArgs(
HELLO_YAML_FILE_PATH,
'/test/files/not_found.ys',
constants.VALID_YAML_DATA,
constants.NOT_FOUND_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_yaml_data_not_found', ValidateArgs(
'./tests/files/not_found.yaml',
HELLO_RULESET_FILE_PATH,
constants.NOT_FOUND_YAML_DATA,
constants.VALID_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_empty_yaml_file_path', ValidateArgs(
'',
HELLO_RULESET_FILE_PATH,
constants.EMPTY_PATH,
constants.VALID_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_empty_ruleset_path', ValidateArgs(
HELLO_YAML_FILE_PATH,
'',
constants.VALID_YAML_DATA,
constants.EMPTY_PATH,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_invalid_ruleset_extension', ValidateArgs(
HELLO_YAML_FILE_PATH,
'./tests/files/hello.ruleset',
constants.VALID_YAML_DATA,
constants.INVALID_SCHEMA_EXTENSION,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_syntax_errors', ValidateArgs(
HELLO_YAML_FILE_PATH,
'./tests/files/invalid_files/invalid_enum_name.ys',
constants.VALID_YAML_DATA,
constants.INVALID_ENUM_NAME_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_ruleset_not_defined', ValidateArgs(
HELLO_YAML_FILE_PATH,
'./tests/files/invalid_files/missing_defined_ruleset.ys',
constants.VALID_YAML_DATA,
constants.MISSING_RULESET_DEF_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.ERR),
('with_self_cycle_in_ruleset', ValidateArgs(
constants.VALID_YAML_DATA,
constants.SELF_CYCLE_SCHEMA,
DisplayMethod.TABLE.value
), SuccessCode.ERR)
])
Expand Down
50 changes: 28 additions & 22 deletions tests/cmd/test_validate_yaml_data_from_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
from yamlator.cmd import validate_yaml_data_from_file
from yamlator.exceptions import InvalidSchemaFilenameError

EMPTY_STR = ''
VALID_YAML_DATA_FILE_PATH = './tests/files/valid/valid.yaml'
VALID_SCHEMA_FILE_PATH = './tests/files/valid/valid.ys'
from tests.cmd import constants


ValidateArgs = namedtuple('ValidateArgs', ['yaml_filepath', 'schema_filepath'])

Expand All @@ -31,34 +30,41 @@ class TestValidateYamlDataFromFile(unittest.TestCase):
"""

@parameterized.expand([
('none_yaml_path',
ValidateArgs(None, VALID_SCHEMA_FILE_PATH), ValueError),
('none_schema_path',
ValidateArgs(VALID_YAML_DATA_FILE_PATH, None), ValueError),
('none_yaml_and_schema_path',
ValidateArgs(None, None), ValueError),
('none_yaml_path', ValidateArgs(
constants.NONE_PATH,
constants.VALID_SCHEMA
), ValueError),
('none_schema_path', ValidateArgs(
constants.VALID_YAML_DATA,
constants.NONE_PATH
), ValueError),
('none_yaml_and_schema_path', ValidateArgs(
constants.NONE_PATH,
constants.NONE_PATH
), ValueError),
('empty_yaml_path_str', ValidateArgs(
EMPTY_STR, VALID_SCHEMA_FILE_PATH
constants.EMPTY_PATH,
constants.VALID_SCHEMA
), ValueError),
('empty_schema_path_str', ValidateArgs(
VALID_YAML_DATA_FILE_PATH,
EMPTY_STR
constants.VALID_YAML_DATA,
constants.EMPTY_PATH
), ValueError),
('empty_yaml_and_path_str', ValidateArgs(
EMPTY_STR,
EMPTY_STR
constants.EMPTY_PATH,
constants.EMPTY_PATH
), ValueError),
('yaml_data_file_not_found', ValidateArgs(
'not_found.yaml',
VALID_SCHEMA_FILE_PATH
constants.NOT_FOUND_YAML_DATA,
constants.VALID_SCHEMA
), FileNotFoundError),
('schema_file_not_found', ValidateArgs(
VALID_YAML_DATA_FILE_PATH,
'not_found.ys'
constants.VALID_YAML_DATA,
constants.NOT_FOUND_SCHEMA
), FileNotFoundError),
('schema_invalid_file_extension', ValidateArgs(
VALID_YAML_DATA_FILE_PATH,
'./tests/files/hello.ruleset'
constants.VALID_YAML_DATA,
constants.INVALID_SCHEMA_EXTENSION
), InvalidSchemaFilenameError)
])
def test_validate_yaml_data_from_file_with_invalid_args(self, name: str,
Expand All @@ -74,8 +80,8 @@ def test_validate_yaml_data_from_file_with_invalid_args(self, name: str,
def test_validate_yaml_data_from_file_with_valid_data(self):
expected_violation_count = 0
violations = validate_yaml_data_from_file(
yaml_filepath=VALID_YAML_DATA_FILE_PATH,
schema_filepath=VALID_SCHEMA_FILE_PATH
yaml_filepath=constants.VALID_YAML_DATA,
schema_filepath=constants.VALID_SCHEMA
)
actual_violation_count = len(violations)

Expand Down
17 changes: 17 additions & 0 deletions tests/files/invalid_files/cycles/common.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Project from "root.ys"

enum Values {
TEST = 1
}

enum Status {
SUCCESS = 0
ERR = 1
FAILURE = 2
WARNING = 3
}

ruleset ProjectDetails {
author str
created str
}
12 changes: 12 additions & 0 deletions tests/files/invalid_files/cycles/root.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Values, Status from "common.ys" as core

ruleset Project {
version str
name str
status core.Status optional
}

schema {
project Project
value core.Values
}
10 changes: 10 additions & 0 deletions tests/files/invalid_files/cycles/self_cycle.ys
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Test from "self_cycle.ys"

ruleset Test {
data str
number int
}

schema {
test Test optional
}
File renamed without changes.
6 changes: 3 additions & 3 deletions tests/files/invalid_files/ruleset_missing_rules.ys
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ruleset Details {}

schema {
message str
details Details
}
}

ruleset Details {}
Loading

0 comments on commit 4cf068a

Please sign in to comment.