diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f879fec..d799ad1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -166,7 +166,7 @@ jobs: python-version: "3.12" - name: "Setup Python environment" run: | - pipx install poetry==1.8.5 + pipx install poetry==2.1 poetry config virtualenvs.create true --local poetry env use 3.12 - name: "Install dependencies" @@ -230,7 +230,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: "Setup environment" run: | - pipx install poetry==1.8.5 --python python${{ matrix.python-version }} + pipx install poetry==2.1 --python python${{ matrix.python-version }} poetry config virtualenvs.create true --local pip install invoke toml codecov - name: "Install Package" @@ -283,14 +283,11 @@ jobs: echo "PYTEST_DEBUG_TEMPROOT=/var/lib/github/${RUNNER_NAME}/_temp" >> $GITHUB_ENV - name: "Setup environment" run: | - pipx install poetry==1.8.5 + pipx install poetry==2.1 poetry config virtualenvs.create true --local pip install invoke toml codecov - name: "Install Package" run: "poetry install --all-extras" - - name: "Set environment variables for python_testcontainers" - run: | - echo INFRAHUB_TESTING_IMAGE_VER=latest >> $GITHUB_ENV - name: "Integration Tests" run: | poetry run pytest --cov infrahub_sdk tests/integration/ @@ -359,7 +356,7 @@ jobs: # - name: "Setup environment" # run: | - # pipx install poetry==1.8.5 + # pipx install poetry==2.1 # poetry config virtualenvs.create true --local # pip install invoke toml codecov diff --git a/.gitignore b/.gitignore index 6d21724f..dd88d992 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ dist/* **/*.csv # Generated files -generated/ \ No newline at end of file +generated/ +sandbox/ \ No newline at end of file diff --git a/.vale/styles/Infrahub/branded-terms-case-swap.yml b/.vale/styles/Infrahub/branded-terms-case-swap.yml index 24b7db60..5fac9b9f 100644 --- a/.vale/styles/Infrahub/branded-terms-case-swap.yml +++ b/.vale/styles/Infrahub/branded-terms-case-swap.yml @@ -6,9 +6,31 @@ ignorecase: false action: name: replace swap: - (?i:[^/]Github): GitHub - (?i:gitpod): GitPod - (?i:[^/]Graphql): GraphQL + (?:ansible): Ansible + (?:[^"]docker): Docker + (?:[Dd]ockerhub): DockerHub + (?:[^/][Gg]ithub): GitHub + (?:[Gg]itlab): GitLab + (?:gitpod): GitPod + (?:grafana): Grafana + (?:[^/][Gg]raphql): GraphQL + (?:[Ii]nflux[Dd]b): InfluxDB infrahub(?:\s|$): Infrahub - (?i:Openconfig): OpenConfig + (?:jinja2): Jinja2 + (?:k3s): K3s + (?:k8s): K8s + (?:kubernetes): Kubernetes + (?:[^/][Mm]y[Ss]ql): MySQL + (?:neo4j): Neo4j + (?:[^/][Nn]ginx): NGINX + (?:[Nn]odejs): Node.js + (?:[^/][Oo]penapi): OpenAPI + (?:Openconfig): OpenConfig opsmill(?:\s|$): OpsMill + (?:[Pp]ostgre[Ss]ql): PostgreSQL + (?:[^/]prometheus): Prometheus + (?:[^/-]python): Python + (?:[Rr]abbitmq): RabbitMQ + (?:[^/]terraform): Terraform + (?:ubuntu): Ubuntu + (?:[Vv]s\W?[Cc]ode): VS Code diff --git a/.yamllint.yml b/.yamllint.yml index aab018dc..65716d9a 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -5,6 +5,9 @@ ignore: | /.venv /examples tests/unit/sdk/test_data/schema_encoding_error.yml + /**/node_modules/** + tests/unit/sdk/test_data/multiple_files_valid_not_valid.yml + tests/fixtures/menus/invalid_yaml.yml rules: new-lines: disable diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c115e6a..1ce592d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,31 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang +## [1.11.0](https://github.com/opsmill/infrahub-sdk-python/tree/v1.11.0) - 2025-04-17 + +### Deprecated + +- The 'timeout' parameter while creating a node or fetching the schema has been deprecated. the default_timeout will be used instead. + +### Added + +- Add support for object Template when generating protocols ([#329](https://github.com/opsmill/infrahub-sdk-python/issues/329)) +- Add a Guide related to Python Typing +- Add method `client.schema.set_cache()` to populate the cache manually (primarily for unit testing) +- By default, schema.fetch will now populate the cache (this behavior can be changed with `populate_cache`) +- Add `menu validate` command to validate the format of menu files. + +### Fixed + +- Raise a proper branch not found error when requesting a node or schema for a branch that doesn't exist. ([#286](https://github.com/opsmill/infrahub-sdk-python/issues/286)) +- Fix support for Sync when generating Python Protocols + +### Housekeeping + +- Add `invoke lint-doc` command to help run the docs linters locally +- Add a fixture to always reset some environment variables before running tests +- Update Pytest-httpx and set all responses as reusable + ## [1.10.2](https://github.com/opsmill/infrahub-sdk-python/tree/v1.10.2) - 2025-04-11 ### Fixed diff --git a/docs/docs/infrahubctl/infrahubctl-menu.mdx b/docs/docs/infrahubctl/infrahubctl-menu.mdx index ef08ce5d..890156d0 100644 --- a/docs/docs/infrahubctl/infrahubctl-menu.mdx +++ b/docs/docs/infrahubctl/infrahubctl-menu.mdx @@ -17,6 +17,7 @@ $ infrahubctl menu [OPTIONS] COMMAND [ARGS]... **Commands**: * `load`: Load one or multiple menu files into... +* `validate`: Validate one or multiple menu files. ## `infrahubctl menu load` @@ -38,3 +39,24 @@ $ infrahubctl menu load [OPTIONS] MENUS... * `--branch TEXT`: Branch on which to load the menu. * `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. + +## `infrahubctl menu validate` + +Validate one or multiple menu files. + +**Usage**: + +```console +$ infrahubctl menu validate [OPTIONS] PATHS... +``` + +**Arguments**: + +* `PATHS...`: [required] + +**Options**: + +* `--debug / --no-debug`: [default: no-debug] +* `--branch TEXT`: Branch on which to validate the objects. +* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml] +* `--help`: Show this message and exit. diff --git a/docs/docs/infrahubctl/infrahubctl-object.mdx b/docs/docs/infrahubctl/infrahubctl-object.mdx index 9ace8cda..ad607e7e 100644 --- a/docs/docs/infrahubctl/infrahubctl-object.mdx +++ b/docs/docs/infrahubctl/infrahubctl-object.mdx @@ -17,6 +17,7 @@ $ infrahubctl object [OPTIONS] COMMAND [ARGS]... **Commands**: * `load`: Load one or multiple objects files into... +* `validate`: Validate one or multiple objects files. ## `infrahubctl object load` @@ -38,3 +39,24 @@ $ infrahubctl object load [OPTIONS] PATHS... * `--branch TEXT`: Branch on which to load the objects. * `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. + +## `infrahubctl object validate` + +Validate one or multiple objects files. + +**Usage**: + +```console +$ infrahubctl object validate [OPTIONS] PATHS... +``` + +**Arguments**: + +* `PATHS...`: [required] + +**Options**: + +* `--debug / --no-debug`: [default: no-debug] +* `--branch TEXT`: Branch on which to validate the objects. +* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml] +* `--help`: Show this message and exit. diff --git a/docs/docs/python-sdk/guides/python-typing.mdx b/docs/docs/python-sdk/guides/python-typing.mdx new file mode 100644 index 00000000..e65e32e8 --- /dev/null +++ b/docs/docs/python-sdk/guides/python-typing.mdx @@ -0,0 +1,103 @@ +--- +title: Strict Typing in Python +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Overview + +This guide explains how to use Python's type system effectively with the Infrahub SDK, focusing on the use of Protocols for type-safe development. + +:::note What is Python Typing + +Python typing allows you to specify the expected data types of variables, function arguments, and return values to improve code clarity and catch bugs early. + +```python +# Basic type hints +def percentage(num1: int, num2: int) -> float: + return (num1 / num2) * 100 +``` + +::: + +## Leveraging Python protocols + +The Python SDK for Infrahub has been designed to automatically work with any schemas loaded into Infrahub. +Internally, the Python SDK generates dynamic Python representations of your schemas. + +While this approach improves code readability, it presents challenges with type checking because each object has a different signature based on your schema. + +### Without protocols + +In the example below, type checkers like Mypy will typically complain about `blue_tag.description.value` because `description` is a dynamic parameter generated by the SDK. + +```python +# Type checker cannot verify the existence of 'description' +blue_tag = client.get("BuiltinTag", name__value="blue") # blue_tag is of type InfrahubNode or InfrahubNodeSync +blue_tag.description.value = "The blue tag" # Mypy: error: "InfrahubNode" has no attribute "description" +blue_tag.save() +``` + +### With protocols + +To provide strict type checking while maintaining platform extensibility, the Python SDK integrates with Python Protocols. + +For all core and internal models, the protocols are included in the SDK under `infrahub_sdk.protocols`. +Whenever you need to specify the kind of object you're working with as a string, you can use the corresponding protocol instead. + +```python +from infrahub_sdk.protocols import BuiltinTag + +# Type checker can now verify all attributes +blue_tag = client.get(BuiltinTag, name__value="blue") # blue_tag is of type BuiltinTag +blue_tag.description.value = "The blue tag" # No type errors +blue_tag.save() +``` + +:::note Python Protocols + +Python Protocols, introduced in PEP 544, define a set of method and property signatures that a class must implement to be considered a match, enabling structural subtyping (also known as "duck typing" with static checks). They allow you to specify behavior without requiring inheritance, making code more flexible and type-safe. + +More information about Python Protocols can be found [here](https://typing.python.org/en/latest/spec/protocol.html) + +::: + +## Generating custom protocols based on your schema + +You can generate Python Protocols for your own models using the `infrahubctl protocols` command. This supports both synchronous and asynchronous Python code. + +It's possible to provide the schema from a local directory or from an existing Infrahub Instance. + + + + + ```shell + export INFRAHUB_ADDRESS=https://infrahub.example.com + infrahubctl protocols --out lib/protocols.py --sync + ``` + + + + + ```shell + infrahubctl protocols --schemas schemas/tag.schema.yml --out lib/protocols.py + ``` + + + + +> When using a local directory, Protocols for Profiles and Object Templates won't be generated. + +## Using custom protocols + +After generation, you can import and use your custom protocols as describe below. + +```python +from lib.protocols import MyOwnObject + +# Use your custom protocol +my_object = client.get(MyOwnObject, name__value="example") +``` + +> if you don't have your own Python module, it's possible to use relative path by having the `procotols.py` in the same directory as your script/transform/generator diff --git a/docs/docs/python-sdk/topics/object_file.mdx b/docs/docs/python-sdk/topics/object_file.mdx new file mode 100644 index 00000000..a383df56 --- /dev/null +++ b/docs/docs/python-sdk/topics/object_file.mdx @@ -0,0 +1,195 @@ +--- +title: Manage data with Object Files +--- + +# Manage data with Object files + +## Introduction + +An Object file is a YAML file that allows you to manage data to be loaded in Infrahub based on your own custom schema. It provides a declarative way to define and manage resources in your Infrahub instance. + +Object files work well for models that don't change too often and/or that need to be tracked in Git. Examples include: Groups, tags, Users, etc. +Below is an example of an Object file that defines tags (`BuiltinTag`). + +```yaml +--- +apiVersion: infrahub.app/v1 +kind: Object +spec: + kind: BuiltinTag + data: + - name: Blue + - name: Yellow + - name: Red +``` + +Object files are meant to be used in an idempotent way and as such they work better for models with a Human Friendly ID (HFID) defined. An HFID is a unique identifier that makes it easier to reference objects across different files and operations. + +## Load Object files into Infrahub + +Object files can be loaded into Infrahub using the `infrahub object load` command. + +```bash +infrahub object load +``` + +Multiple object files can be loaded at once by specifying the path to multiple files or by specifying a directory. + +The `object load` command will create/update the objects using an `Upsert` operation. All objects previously loaded will NOT be deleted in the Infrahub instance. +Also, if some objects present in different files are identical and dependent on each other, the `object load` command will NOT calculate the dependencies between the objects and as such it's the responsibility of the users to execute the command in the right order. + +### Validate the format of object files + +The object file can be validated using the `infrahub object validate` command. + +```bash +infrahub object validate +``` + +## Object file format + +All object files must start with the following format, all other formats will be automatically ignored. +Each file is intended for one specific top level kind, but one file can include multiple nested objects of any kind. +The kind of the top level object must be defined in spec/kind. + +```yaml +--- +apiVersion: infrahub.app/v1 +kind: Object +spec: + kind: + data: + - [...] +``` + +> Multiple documents in a single YAML file are also supported, each document will be loaded separately. Documents are separated by `---` + +### Relationship of cardinality one + +A relationship of cardinality one can either reference an existing node via its HFID or create a new node if it doesn't exist. +In the example below, both `site` and `primary_ip` are relationships of cardinality one. + +```yaml +--- +apiVersion: infrahub.app/v1 +kind: Object +spec: + kind: InfraDevice + data: + - name: edge01 + site: "Paris" # Reference existing node via its HFID + primary_ip: # Nested object, will be created if it doesn't exist + data: + address: "192.168.1.1" +``` + +### Relationship of cardinality many + +A relationship of cardinality many can reference existing nodes via their HFID or define nested objects. + +#### Existing nodes referenced by their HFID + +Existing nodes can be referenced by their HFID in string format or in list format. +In the example below, both `best_friends` and `tags` are relationships of cardinality many. + +> An HFID is composed of a single value, it's possible to use a string instead of a list + +```yaml +--- +apiVersion: infrahub.app/v1 +kind: Object +spec: + kind: TestingPerson + data: + - name: Mike Johnson + height: 175 + best_friends: # Relationship of cardinality many that references existing nodes based on their HFID + - [Jane Smith, Max] + - [Sarah Williams, Charlie] + tags: + - Veterinarian # Existing Node referenced by its HFID in string format + - [Breeder] # Existing Node referenced by its HFID in list format +``` + +#### Nested objects + +When defining nested objects, the node will be automatically created if it doesn't exist and if the relationship between the parent object and the nested object exists, it will be automatically inserted. +For example, in the example below, the `owner` of a `TestingDog` doesn't need to be specified because it will be automatically inserted. + +Two different syntax are supported: + +- A dictionary with multiple values under data +- A list of objects + +##### Nested objects as a dictionary + +In the example below, `tags` is a relationship of cardinality many that is defined as a dictionary with multiple values under data. + +> The kind is optional here because there is only one option possible (not a generic) + +```yaml +--- +apiVersion: infrahub.app/v1 +kind: Object +spec: + kind: TestingPerson + data: + - name: Alex Thompson + tags: + data: + - name: dog-lover + description: "Dog Lover" + - name: veterinarian + description: "Veterinarian" +``` + +This format works well when all objects are of the same kind and when all objects are using the same properties. +For more complex cases, the list of objects format is more flexible. + +##### Nested objects as a list of objects + +In the example below, `animals` is a relationship of cardinality many that is defined as a list of objects. +Each object must contain a `data` key and each object can also define a specific `kind`. + +> If the kind is not specified, it will be inferred from schema + +```yaml +--- +apiVersion: infrahub.app/v1 +kind: Object +spec: + kind: TestingPerson + data: + - name: Alex Thompson + height: 180 + animals: + - kind: TestingDog + data: + name: Max + weight: 25 + breed: Golden Retriever + color: "#FFD700" + - kind: TestingCat + data: + name: Mimi + breed: Persian +``` + +### Support for metadata + +Metadata support is planned for future releases. Currently, the Object file does not support metadata on attributes or relationships. + +## Troubleshooting + +### Common issues + +1. **Objects not being created**: Ensure that the YAML syntax is correct and that the file follows the required format. +2. **Dependency errors**: When objects depend on each other, load them in the correct order (dependencies first). +3. **Validation errors**: Use the `infrahub object validate` command to check for syntax errors before loading. + +### Best practices + +1. Use Human Friendly IDs (HFIDs) for all objects to ensure consistent referencing. +2. Keep object files organized by model type or purpose. +3. Validate object files before loading them into production environments. +4. Use comments in your YAML files to document complex relationships or dependencies. diff --git a/docs/sidebars-python-sdk.ts b/docs/sidebars-python-sdk.ts index 7cde4058..e2fc932c 100644 --- a/docs/sidebars-python-sdk.ts +++ b/docs/sidebars-python-sdk.ts @@ -21,6 +21,7 @@ const sidebars: SidebarsConfig = { 'guides/branches', 'guides/store', 'guides/tracking', + 'guides/python-typing', 'guides/batch', 'guides/object-storage', 'guides/resource-manager', @@ -31,6 +32,7 @@ const sidebars: SidebarsConfig = { label: 'Topics', items: [ 'topics/tracking', + 'topics/object_file', ], }, { diff --git a/infrahub_sdk/checks.py b/infrahub_sdk/checks.py index ad692537..79511ccb 100644 --- a/infrahub_sdk/checks.py +++ b/infrahub_sdk/checks.py @@ -83,7 +83,7 @@ def client(self, value: InfrahubClient) -> None: async def init(cls, client: InfrahubClient | None = None, *args: Any, **kwargs: Any) -> InfrahubCheck: """Async init method, If an existing InfrahubClient client hasn't been provided, one will be created automatically.""" warnings.warn( - "InfrahubCheck.init has been deprecated and will be removed in the version in Infrahub SDK 2.0.0", + "InfrahubCheck.init has been deprecated and will be removed in version 2.0.0 of the Infrahub Python SDK", DeprecationWarning, stacklevel=1, ) diff --git a/infrahub_sdk/ctl/cli_commands.py b/infrahub_sdk/ctl/cli_commands.py index 0d9a850f..13910621 100644 --- a/infrahub_sdk/ctl/cli_commands.py +++ b/infrahub_sdk/ctl/cli_commands.py @@ -20,7 +20,6 @@ from .. import __version__ as sdk_version from ..async_typer import AsyncTyper -from ..code_generator import CodeGenerator from ..ctl import config from ..ctl.branch import app as branch_app from ..ctl.check import run as run_check @@ -42,6 +41,7 @@ ) from ..ctl.validate import app as validate_app from ..exceptions import GraphQLError, ModuleImportError +from ..protocols_generator.generator import CodeGenerator from ..schema import MainSchemaTypesAll, SchemaRoot from ..template import Jinja2Template from ..template.exceptions import JinjaTemplateError @@ -61,7 +61,7 @@ app.add_typer(validate_app, name="validate") app.add_typer(repository_app, name="repository") app.add_typer(menu_app, name="menu") -app.add_typer(object_app, name="object", hidden=True) +app.add_typer(object_app, name="object") app.command(name="dump")(dump) app.command(name="load")(load) diff --git a/infrahub_sdk/ctl/constants.py b/infrahub_sdk/ctl/constants.py deleted file mode 100644 index 8d797150..00000000 --- a/infrahub_sdk/ctl/constants.py +++ /dev/null @@ -1,115 +0,0 @@ -PROTOCOLS_TEMPLATE = """# -# Generated by "infrahubctl protocols" -# - -from __future__ import annotations - -from typing import TYPE_CHECKING, Optional - -from infrahub_sdk.protocols import CoreNode, {{ base_protocols | join(', ') }} - -if TYPE_CHECKING: - {% if sync %} - from infrahub_sdk.node import RelatedNodeSync, RelationshipManagerSync - {% else %} - from infrahub_sdk.node import RelatedNode, RelationshipManager - {% endif %} - from infrahub_sdk.protocols_base import ( - AnyAttribute, - AnyAttributeOptional, - String, - StringOptional, - Integer, - IntegerOptional, - Boolean, - BooleanOptional, - DateTime, - DateTimeOptional, - Dropdown, - DropdownOptional, - HashedPassword, - HashedPasswordOptional, - MacAddress, - MacAddressOptional, - IPHost, - IPHostOptional, - IPNetwork, - IPNetworkOptional, - JSONAttribute, - JSONAttributeOptional, - ListAttribute, - ListAttributeOptional, - URL, - URLOptional, - ) -{% for generic in generics %} - - -class {{ generic.namespace + generic.name }}(CoreNode): - {% if not generic.attributes|default([]) and not generic.relationships|default([]) %} - pass - {% endif %} - {% for attribute in generic.attributes|default([]) %} - {{ attribute | render_attribute }} - {% endfor %} - {% for relationship in generic.relationships|default([]) %} - {{ relationship | render_relationship(sync) }} - {% endfor %} - {% if generic.hierarchical | default(false) %} - {% if sync %} - parent: RelatedNodeSync - children: RelationshipManagerSync - {% else %} - parent: RelatedNode - children: RelationshipManager - {% endif %} - {% endif %} -{% endfor %} -{% for node in nodes %} - - -class {{ node.namespace + node.name }}({{ node.inherit_from | join(", ") or "CoreNode" }}): - {% if not node.attributes|default([]) and not node.relationships|default([]) %} - pass - {% endif %} - {% for attribute in node.attributes|default([]) %} - {{ attribute | render_attribute }} - {% endfor %} - {% for relationship in node.relationships|default([]) %} - {{ relationship | render_relationship(sync) }} - {% endfor %} - {% if node.hierarchical | default(false) %} - {% if sync %} - parent: RelatedNodeSync - children: RelationshipManagerSync - {% else %} - parent: RelatedNode - children: RelationshipManager - {% endif %} - {% endif %} -{% endfor %} -{% for node in profiles %} - - -class {{ node.namespace + node.name }}({{ node.inherit_from | join(", ") or "CoreNode" }}): - {% if not node.attributes|default([]) and not node.relationships|default([]) %} - pass - {% endif %} - {% for attribute in node.attributes|default([]) %} - {{ attribute | render_attribute }} - {% endfor %} - {% for relationship in node.relationships|default([]) %} - {{ relationship | render_relationship(sync) }} - {% endfor %} - {% if node.hierarchical | default(false) %} - {% if sync %} - parent: RelatedNodeSync - children: RelationshipManagerSync - {% else %} - parent: RelatedNode - children: RelationshipManager - {% endif %} - {% endif %} -{% endfor %} - -""" diff --git a/infrahub_sdk/ctl/menu.py b/infrahub_sdk/ctl/menu.py index 560564ed..ff702f8d 100644 --- a/infrahub_sdk/ctl/menu.py +++ b/infrahub_sdk/ctl/menu.py @@ -7,9 +7,14 @@ from ..async_typer import AsyncTyper from ..ctl.client import initialize_client from ..ctl.utils import catch_exception, init_logging +from ..exceptions import ObjectValidationError, ValidationError from ..spec.menu import MenuFile from .parameters import CONFIG_PARAM -from .utils import load_yamlfile_from_disk_and_exit +from .utils import ( + display_object_validate_format_error, + display_object_validate_format_success, + load_yamlfile_from_disk_and_exit, +) app = AsyncTyper() console = Console() @@ -39,16 +44,54 @@ async def load( files = load_yamlfile_from_disk_and_exit(paths=menus, file_type=MenuFile, console=console) client = initialize_client() + has_errors = False + + for file in files: + try: + await file.validate_format(client=client, branch=branch) + except ValidationError as exc: + has_errors = True + display_object_validate_format_error(file=file, error=exc, console=console) + + if has_errors: + raise typer.Exit(1) + for file in files: - file.validate_content() - schema = await client.schema.get(kind=file.spec.kind, branch=branch) - - for idx, item in enumerate(file.spec.data): - await file.spec.create_node( - client=client, - schema=schema, - data=item, - branch=branch, - default_schema_kind=file.spec.kind, - context={"list_index": idx}, - ) + try: + await file.process(client=client, branch=branch) + except ObjectValidationError as exc: + has_errors = True + console.print(f"[red] {exc!s}") + + if has_errors: + raise typer.Exit(1) + + +@app.command() +@catch_exception(console=console) +async def validate( + paths: list[Path], + debug: bool = False, + branch: str = typer.Option(None, help="Branch on which to validate the objects."), + _: str = CONFIG_PARAM, +) -> None: + """Validate one or multiple menu files.""" + + init_logging(debug=debug) + + logging.getLogger("infrahub_sdk").setLevel(logging.INFO) + + files = load_yamlfile_from_disk_and_exit(paths=paths, file_type=MenuFile, console=console) + client = initialize_client() + + has_errors = False + for file in files: + try: + await file.validate_format(client=client, branch=branch) + display_object_validate_format_success(file=file, console=console) + except ValidationError as exc: + has_errors = True + display_object_validate_format_error(file=file, error=exc, console=console) + + if has_errors: + raise typer.Exit(1) diff --git a/infrahub_sdk/ctl/object.py b/infrahub_sdk/ctl/object.py index b589fcc2..79c6aa3d 100644 --- a/infrahub_sdk/ctl/object.py +++ b/infrahub_sdk/ctl/object.py @@ -7,9 +7,14 @@ from ..async_typer import AsyncTyper from ..ctl.client import initialize_client from ..ctl.utils import catch_exception, init_logging +from ..exceptions import ObjectValidationError, ValidationError from ..spec.object import ObjectFile from .parameters import CONFIG_PARAM -from .utils import load_yamlfile_from_disk_and_exit +from .utils import ( + display_object_validate_format_error, + display_object_validate_format_success, + load_yamlfile_from_disk_and_exit, +) app = AsyncTyper() console = Console() @@ -39,9 +44,54 @@ async def load( files = load_yamlfile_from_disk_and_exit(paths=paths, file_type=ObjectFile, console=console) client = initialize_client() + has_errors = False + + for file in files: + try: + await file.validate_format(client=client, branch=branch) + except ValidationError as exc: + has_errors = True + display_object_validate_format_error(file=file, error=exc, console=console) + + if has_errors: + raise typer.Exit(1) + + for file in files: + try: + await file.process(client=client, branch=branch) + except ObjectValidationError as exc: + has_errors = True + console.print(f"[red] {exc!s}") + + if has_errors: + raise typer.Exit(1) + + +@app.command() +@catch_exception(console=console) +async def validate( + paths: list[Path], + debug: bool = False, + branch: str = typer.Option(None, help="Branch on which to validate the objects."), + _: str = CONFIG_PARAM, +) -> None: + """Validate one or multiple objects files.""" + + init_logging(debug=debug) + + logging.getLogger("infrahub_sdk").setLevel(logging.INFO) + + files = load_yamlfile_from_disk_and_exit(paths=paths, file_type=ObjectFile, console=console) + client = initialize_client() + + has_errors = False for file in files: - file.validate_content() - schema = await client.schema.get(kind=file.spec.kind, branch=branch) + try: + await file.validate_format(client=client, branch=branch) + display_object_validate_format_success(file=file, console=console) + except ValidationError as exc: + has_errors = True + display_object_validate_format_error(file=file, error=exc, console=console) - for item in file.spec.data: - await file.spec.create_node(client=client, schema=schema, data=item, branch=branch) + if has_errors: + raise typer.Exit(1) diff --git a/infrahub_sdk/ctl/utils.py b/infrahub_sdk/ctl/utils.py index 898095c7..63d7dfb8 100644 --- a/infrahub_sdk/ctl/utils.py +++ b/infrahub_sdk/ctl/utils.py @@ -25,6 +25,7 @@ SchemaNotFoundError, ServerNotReachableError, ServerNotResponsiveError, + ValidationError, ) from ..yaml import YamlFile from .client import initialize_client_sync @@ -32,6 +33,7 @@ if TYPE_CHECKING: from ..schema.repository import InfrahubRepositoryConfig + from ..spec.object import ObjectFile YamlFileVar = TypeVar("YamlFileVar", bound=YamlFile) T = TypeVar("T") @@ -198,4 +200,23 @@ def load_yamlfile_from_disk_and_exit( if has_error: raise typer.Exit(1) - return data_files + return sorted(data_files, key=lambda x: x.location) + + +def display_object_validate_format_success(file: ObjectFile, console: Console) -> None: + if file.multiple_documents: + console.print(f"[green] File '{file.location}' [{file.document_position}] is Valid!") + else: + console.print(f"[green] File '{file.location}' is Valid!") + + +def display_object_validate_format_error(file: ObjectFile, error: ValidationError, console: Console) -> None: + if file.multiple_documents: + console.print(f"[red] File '{file.location}' [{file.document_position}] is not valid!") + else: + console.print(f"[red] File '{file.location}' is not valid!") + if error.messages: + for message in error.messages: + console.print(f"[red] {message}") + else: + console.print(f"[red] {error.message}") diff --git a/infrahub_sdk/exceptions.py b/infrahub_sdk/exceptions.py index f8a5b541..a8b1ef9b 100644 --- a/infrahub_sdk/exceptions.py +++ b/infrahub_sdk/exceptions.py @@ -113,11 +113,29 @@ def __init__(self, name: str, message: str | None = None): class ValidationError(Error): - def __init__(self, identifier: str, message: str): + def __init__(self, identifier: str, message: str | None = None, messages: list[str] | None = None): self.identifier = identifier self.message = message + self.messages = messages + if not messages and not message: + self.message = f"Validation Error for {self.identifier}" super().__init__(self.message) + def __str__(self) -> str: + if self.messages: + return f"{self.identifier}: {', '.join(self.messages)}" + return f"{self.identifier}: {self.message}" + + +class ObjectValidationError(Error): + def __init__(self, position: list[int | str], message: str): + self.position = position + self.message = message + super().__init__(self.message) + + def __str__(self) -> str: + return f"{'.'.join(map(str, self.position))}: {self.message}" + class AuthenticationError(Error): def __init__(self, message: str | None = None): diff --git a/infrahub_sdk/node.py b/infrahub_sdk/node.py index 161d31bb..8628e7cc 100644 --- a/infrahub_sdk/node.py +++ b/infrahub_sdk/node.py @@ -793,13 +793,12 @@ def hfid_str(self) -> str | None: return self.get_human_friendly_id_as_string(include_kind=True) def _init_attributes(self, data: dict | None = None) -> None: - for attr_name in self._attributes: - attr_schema = [attr for attr in self._schema.attributes if attr.name == attr_name][0] - attr_data = data.get(attr_name, None) if isinstance(data, dict) else None + for attr_schema in self._schema.attributes: + attr_data = data.get(attr_schema.name, None) if isinstance(data, dict) else None setattr( self, - attr_name, - Attribute(name=attr_name, schema=attr_schema, data=attr_data), + attr_schema.name, + Attribute(name=attr_schema.name, schema=attr_schema, data=attr_data), ) def _get_request_context(self, request_context: RequestContext | None = None) -> dict[str, Any] | None: @@ -1147,24 +1146,23 @@ async def from_graphql( return cls(client=client, schema=schema, branch=branch, data=cls._strip_alias(data)) def _init_relationships(self, data: dict | None = None) -> None: - for rel_name in self._relationships: - rel_schema = [rel for rel in self._schema.relationships if rel.name == rel_name][0] - rel_data = data.get(rel_name, None) if isinstance(data, dict) else None + for rel_schema in self._schema.relationships: + rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None if rel_schema.cardinality == "one": - setattr(self, f"_{rel_name}", None) + setattr(self, f"_{rel_schema.name}", None) setattr( self.__class__, - rel_name, - generate_relationship_property(name=rel_name, node=self), + rel_schema.name, + generate_relationship_property(name=rel_schema.name, node=self), ) - setattr(self, rel_name, rel_data) + setattr(self, rel_schema.name, rel_data) else: setattr( self, - rel_name, + rel_schema.name, RelationshipManager( - name=rel_name, + name=rel_schema.name, client=self._client, node=self, branch=self._branch, @@ -1679,24 +1677,23 @@ def from_graphql( return cls(client=client, schema=schema, branch=branch, data=cls._strip_alias(data)) def _init_relationships(self, data: dict | None = None) -> None: - for rel_name in self._relationships: - rel_schema = [rel for rel in self._schema.relationships if rel.name == rel_name][0] - rel_data = data.get(rel_name, None) if isinstance(data, dict) else None + for rel_schema in self._schema.relationships: + rel_data = data.get(rel_schema.name, None) if isinstance(data, dict) else None if rel_schema.cardinality == "one": - setattr(self, f"_{rel_name}", None) + setattr(self, f"_{rel_schema.name}", None) setattr( self.__class__, - rel_name, - generate_relationship_property(name=rel_name, node=self), + rel_schema.name, + generate_relationship_property(name=rel_schema.name, node=self), ) - setattr(self, rel_name, rel_data) + setattr(self, rel_schema.name, rel_data) else: setattr( self, - rel_name, + rel_schema.name, RelationshipManagerSync( - name=rel_name, + name=rel_schema.name, client=self._client, node=self, branch=self._branch, diff --git a/infrahub_sdk/protocols_generator/__init__.py b/infrahub_sdk/protocols_generator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/infrahub_sdk/protocols_generator/constants.py b/infrahub_sdk/protocols_generator/constants.py new file mode 100644 index 00000000..d0bdb076 --- /dev/null +++ b/infrahub_sdk/protocols_generator/constants.py @@ -0,0 +1,28 @@ +TEMPLATE_FILE_NAME = "template.j2" + +ATTRIBUTE_KIND_MAP = { + "ID": "String", + "Text": "String", + "TextArea": "String", + "DateTime": "DateTime", + "Email": "String", + "Password": "String", + "HashedPassword": "HashedPassword", + "URL": "URL", + "File": "String", + "MacAddress": "MacAddress", + "Color": "String", + "Dropdown": "Dropdown", + "Number": "Integer", + "Bandwidth": "Integer", + "IPHost": "IPHost", + "IPNetwork": "IPNetwork", + "Boolean": "Boolean", + "Checkbox": "Boolean", + "List": "ListAttribute", + "JSON": "JSONAttribute", + "Any": "AnyAttribute", +} + +# The order of the classes in the list determines the order of the classes in the generated code +CORE_BASE_CLASS_TO_SYNCIFY = ["CoreProfile", "CoreObjectTemplate", "CoreNode"] diff --git a/infrahub_sdk/code_generator.py b/infrahub_sdk/protocols_generator/generator.py similarity index 63% rename from infrahub_sdk/code_generator.py rename to infrahub_sdk/protocols_generator/generator.py index 6dba2433..e6fe0ce3 100644 --- a/infrahub_sdk/code_generator.py +++ b/infrahub_sdk/protocols_generator/generator.py @@ -1,13 +1,12 @@ from __future__ import annotations from collections.abc import Mapping -from typing import Any +from pathlib import Path import jinja2 -from . import protocols as sdk_protocols -from .ctl.constants import PROTOCOLS_TEMPLATE -from .schema import ( +from .. import protocols as sdk_protocols +from ..schema import ( AttributeSchemaAPI, GenericSchema, GenericSchemaAPI, @@ -16,31 +15,22 @@ NodeSchemaAPI, ProfileSchemaAPI, RelationshipSchemaAPI, + TemplateSchemaAPI, ) +from .constants import ATTRIBUTE_KIND_MAP, CORE_BASE_CLASS_TO_SYNCIFY, TEMPLATE_FILE_NAME -ATTRIBUTE_KIND_MAP = { - "ID": "String", - "Text": "String", - "TextArea": "String", - "DateTime": "DateTime", - "Email": "String", - "Password": "String", - "HashedPassword": "HashedPassword", - "URL": "URL", - "File": "String", - "MacAddress": "MacAddress", - "Color": "String", - "Dropdown": "Dropdown", - "Number": "Integer", - "Bandwidth": "Integer", - "IPHost": "IPHost", - "IPNetwork": "IPNetwork", - "Boolean": "Boolean", - "Checkbox": "Boolean", - "List": "ListAttribute", - "JSON": "JSONAttribute", - "Any": "AnyAttribute", -} + +def load_template() -> str: + path = Path(__file__).parent / TEMPLATE_FILE_NAME + return path.read_text() + + +def move_to_end_of_list(lst: list, item: str) -> list: + """Move an item to the end of a list if it exists in the list""" + if item in lst: + lst.remove(item) + lst.append(item) + return lst class CodeGenerator: @@ -48,6 +38,7 @@ def __init__(self, schema: dict[str, MainSchemaTypesAll]): self.generics: dict[str, GenericSchemaAPI | GenericSchema] = {} self.nodes: dict[str, NodeSchemaAPI | NodeSchema] = {} self.profiles: dict[str, ProfileSchemaAPI] = {} + self.templates: dict[str, TemplateSchemaAPI] = {} for name, schema_type in schema.items(): if isinstance(schema_type, (GenericSchemaAPI, GenericSchema)): @@ -56,6 +47,8 @@ def __init__(self, schema: dict[str, MainSchemaTypesAll]): self.nodes[name] = schema_type if isinstance(schema_type, ProfileSchemaAPI): self.profiles[name] = schema_type + if isinstance(schema_type, TemplateSchemaAPI): + self.templates[name] = schema_type self.base_protocols = [ e @@ -71,29 +64,49 @@ def __init__(self, schema: dict[str, MainSchemaTypesAll]): self.sorted_profiles = self._sort_and_filter_models( self.profiles, filters=["CoreProfile"] + self.base_protocols ) + self.sorted_templates = self._sort_and_filter_models( + self.templates, filters=["CoreObjectTemplate"] + self.base_protocols + ) def render(self, sync: bool = True) -> str: jinja2_env = jinja2.Environment(loader=jinja2.BaseLoader(), trim_blocks=True, lstrip_blocks=True) - jinja2_env.filters["inheritance"] = self._jinja2_filter_inheritance jinja2_env.filters["render_attribute"] = self._jinja2_filter_render_attribute jinja2_env.filters["render_relationship"] = self._jinja2_filter_render_relationship + jinja2_env.filters["syncify"] = self._jinja2_filter_syncify - template = jinja2_env.from_string(PROTOCOLS_TEMPLATE) + template = jinja2_env.from_string(load_template()) return template.render( generics=self.sorted_generics, nodes=self.sorted_nodes, profiles=self.sorted_profiles, + templates=self.sorted_templates, base_protocols=self.base_protocols, + core_node_name="CoreNodeSync" if sync else "CoreNode", sync=sync, ) @staticmethod - def _jinja2_filter_inheritance(value: dict[str, Any]) -> str: - inherit_from: list[str] = value.get("inherit_from", []) + def _jinja2_filter_syncify(value: str | list, sync: bool = False) -> str | list: + """Filter to help with the convertion to sync + + If a string is provided, append Sync to the end of the string + If a list is provided, search for CoreNode and replace it with CoreNodeSync + """ + if isinstance(value, list): + # Order the list based on the CORE_BASE_CLASS_TO_SYNCIFY list to ensure the base classes are always last + for item in CORE_BASE_CLASS_TO_SYNCIFY: + value = move_to_end_of_list(value, item) + + if not sync: + return value + + if isinstance(value, str): + return f"{value}Sync" + + if isinstance(value, list): + return [f"{item}Sync" if item in CORE_BASE_CLASS_TO_SYNCIFY else item for item in value] - if not inherit_from: - return "CoreNode" - return ", ".join(inherit_from) + return value @staticmethod def _jinja2_filter_render_attribute(value: AttributeSchemaAPI) -> str: diff --git a/infrahub_sdk/protocols_generator/template.j2 b/infrahub_sdk/protocols_generator/template.j2 new file mode 100644 index 00000000..2f4f9e89 --- /dev/null +++ b/infrahub_sdk/protocols_generator/template.j2 @@ -0,0 +1,114 @@ +# +# Generated by "infrahubctl protocols" +# + +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from infrahub_sdk.protocols import {{ "CoreNode" | syncify(sync) }}, {{ base_protocols | join(', ') }} + +if TYPE_CHECKING: + from infrahub_sdk.node import {{ "RelatedNode" | syncify(sync) }}, {{ "RelationshipManager" | syncify(sync) }} + from infrahub_sdk.protocols_base import ( + AnyAttribute, + AnyAttributeOptional, + String, + StringOptional, + Integer, + IntegerOptional, + Boolean, + BooleanOptional, + DateTime, + DateTimeOptional, + Dropdown, + DropdownOptional, + HashedPassword, + HashedPasswordOptional, + MacAddress, + MacAddressOptional, + IPHost, + IPHostOptional, + IPNetwork, + IPNetworkOptional, + JSONAttribute, + JSONAttributeOptional, + ListAttribute, + ListAttributeOptional, + URL, + URLOptional, + ) + +{% for generic in generics %} + +class {{ generic.namespace + generic.name }}({{core_node_name}}): + {% if not generic.attributes|default([]) and not generic.relationships|default([]) %} + pass + {% endif %} + {% for attribute in generic.attributes | sort(attribute='name') | default([]) %} + {{ attribute | render_attribute }} + {% endfor %} + {% for relationship in generic.relationships | sort(attribute='name') | default([]) %} + {{ relationship | render_relationship(sync) }} + {% endfor %} + {% if generic.hierarchical | default(false) %} + parent: {{ "RelatedNode" | syncify(sync) }} + children: {{ "RelationshipManager" | syncify(sync) }} + {% endif %} +{% endfor %} + + +{% for node in nodes %} + +class {{ node.namespace + node.name }}({{ node.inherit_from | syncify(sync) | join(", ") or core_node_name }}): + {% if not node.attributes|default([]) and not node.relationships|default([]) %} + pass + {% endif %} + {% for attribute in node.attributes | sort(attribute='name') | default([]) %} + {{ attribute | render_attribute }} + {% endfor %} + {% for relationship in node.relationships | sort(attribute='name') | default([]) %} + {{ relationship | render_relationship(sync) }} + {% endfor %} + {% if node.hierarchical | default(false) %} + parent: {{ "RelatedNode" | syncify(sync) }} + children: {{ "RelationshipManager" | syncify(sync) }} + {% endif %} + +{% endfor %} + + +{% for node in profiles %} + +class {{ node.namespace + node.name }}({{ node.inherit_from | syncify(sync) | join(", ") or core_node_name }}): + {% if not node.attributes|default([]) and not node.relationships|default([]) %} + pass + {% endif %} + {% for attribute in node.attributes | sort(attribute='name') | default([]) %} + {{ attribute | render_attribute }} + {% endfor %} + {% for relationship in node.relationships | sort(attribute='name') | default([]) %} + {{ relationship | render_relationship(sync) }} + {% endfor %} + {% if node.hierarchical | default(false) %} + parent: {{ "RelatedNode" | syncify(sync) }} + children: {{ "RelationshipManager" | syncify(sync) }} + {% endif %} + +{% endfor %} + + +{% for node in templates %} + +class {{ node.namespace + node.name }}({{ node.inherit_from | syncify(sync) | join(", ") or core_node_name }}): + {% if not node.attributes|default([]) and not node.relationships|default([]) %} + pass + {% endif %} + {% for attribute in node.attributes | sort(attribute='name') | default([]) %} + {{ attribute | render_attribute }} + {% endfor %} + {% for relationship in node.relationships | sort(attribute='name') | default([]) %} + {{ relationship | render_relationship(sync) }} + {% endfor %} + +{% endfor %} \ No newline at end of file diff --git a/infrahub_sdk/schema/__init__.py b/infrahub_sdk/schema/__init__.py index 3a8773bb..be1cfab9 100644 --- a/infrahub_sdk/schema/__init__.py +++ b/infrahub_sdk/schema/__init__.py @@ -2,6 +2,7 @@ import asyncio import json +import warnings from collections.abc import MutableMapping from enum import Enum from time import sleep @@ -13,6 +14,7 @@ from typing_extensions import TypeAlias from ..exceptions import ( + BranchNotFoundError, InvalidResponseError, JsonDecodeError, SchemaNotFoundError, @@ -89,6 +91,13 @@ class EnumMutation(str, Enum): class InfrahubSchemaBase: + client: InfrahubClient | InfrahubClientSync + cache: dict[str, BranchSchema] + + def __init__(self, client: InfrahubClient | InfrahubClientSync): + self.client = client + self.cache = {} + def validate(self, data: dict[str, Any]) -> None: SchemaRoot(**data) @@ -101,6 +110,23 @@ def validate_data_against_schema(self, schema: MainSchemaTypesAPI, data: dict) - message=f"{key} is not a valid value for {identifier}", ) + def set_cache(self, schema: dict[str, Any] | SchemaRootAPI | BranchSchema, branch: str | None = None) -> None: + """ + Set the cache manually (primarily for unit testing) + + Args: + schema: The schema to set the cache as provided by the /api/schema endpoint either in dict or SchemaRootAPI format + branch: The name of the branch to set the cache for. + """ + branch = branch or self.client.default_branch + + if isinstance(schema, SchemaRootAPI): + schema = BranchSchema.from_schema_root_api(data=schema) + elif isinstance(schema, dict): + schema = BranchSchema.from_api_response(data=schema) + + self.cache[branch] = schema + def generate_payload_create( self, schema: MainSchemaTypesAPI, @@ -167,11 +193,37 @@ def _get_schema_name(schema: type[SchemaType | SchemaTypeSync] | str) -> str: raise ValueError("schema must be a protocol or a string") + @staticmethod + def _parse_schema_response(response: httpx.Response, branch: str) -> MutableMapping[str, Any]: + if response.status_code == 400: + raise BranchNotFoundError( + identifier=branch, message=f"The requested branch was not found on the server [{branch}]" + ) + response.raise_for_status() + + try: + data: MutableMapping[str, Any] = response.json() + except json.decoder.JSONDecodeError as exc: + raise JsonDecodeError( + message=f"Invalid Schema response received from the server at {response.url}: {response.text} [{response.status_code}] ", + content=response.text, + url=str(response.url), + ) from exc + + return data + + @staticmethod + def _deprecated_schema_timeout() -> None: + warnings.warn( + "The 'timeout' parameter is deprecated while fetching the schema and will be removed version 2.0.0 of the Infrahub Python SDK. " + "Use client.default_timeout instead.", + DeprecationWarning, + stacklevel=2, + ) + class InfrahubSchema(InfrahubSchemaBase): - def __init__(self, client: InfrahubClient): - self.client = client - self.cache: dict[str, BranchSchema] = {} + client: InfrahubClient async def get( self, @@ -184,8 +236,11 @@ async def get( kind_str = self._get_schema_name(schema=kind) + if timeout: + self._deprecated_schema_timeout() + if refresh: - self.cache[branch] = await self._fetch(branch=branch, timeout=timeout) + self.cache[branch] = await self._fetch(branch=branch) if branch in self.cache and kind_str in self.cache[branch].nodes: return self.cache[branch].nodes[kind_str] @@ -193,7 +248,7 @@ async def get( # Fetching the latest schema from the server if we didn't fetch it earlier # because we coulnd't find the object on the local cache if not refresh: - self.cache[branch] = await self._fetch(branch=branch, timeout=timeout) + self.cache[branch] = await self._fetch(branch=branch) if branch in self.cache and kind_str in self.cache[branch].nodes: return self.cache[branch].nodes[kind_str] @@ -396,67 +451,45 @@ async def add_dropdown_option( ) async def fetch( - self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None + self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None, populate_cache: bool = True ) -> MutableMapping[str, MainSchemaTypesAPI]: """Fetch the schema from the server for a given branch. Args: - branch (str): Name of the branch to fetch the schema for. - timeout (int, optional): Overrides default timeout used when querying the GraphQL API. Specified in seconds. + branch: Name of the branch to fetch the schema for. + timeout: Overrides default timeout used when querying the schema. deprecated. + populate_cache: Whether to populate the cache with the fetched schema. Defaults to True. Returns: dict[str, MainSchemaTypes]: Dictionary of all schema organized by kind """ - branch_schema = await self._fetch(branch=branch, namespaces=namespaces, timeout=timeout) + + if timeout: + self._deprecated_schema_timeout() + + branch_schema = await self._fetch(branch=branch, namespaces=namespaces) + + if populate_cache: + self.cache[branch] = branch_schema + return branch_schema.nodes - async def _fetch( - self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None - ) -> BranchSchema: + async def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema: url_parts = [("branch", branch)] if namespaces: url_parts.extend([("namespaces", ns) for ns in namespaces]) query_params = urlencode(url_parts) url = f"{self.client.address}/api/schema?{query_params}" - response = await self.client._get(url=url, timeout=timeout) - response.raise_for_status() - - try: - data: MutableMapping[str, Any] = response.json() - except json.decoder.JSONDecodeError as exc: - raise JsonDecodeError( - message=f"Invalid Schema response received from the server at {response.url}: {response.text} [{response.status_code}] ", - content=response.text, - url=response.url, - ) from exc - - nodes: MutableMapping[str, MainSchemaTypesAPI] = {} - for node_schema in data.get("nodes", []): - node = NodeSchemaAPI(**node_schema) - nodes[node.kind] = node - - for generic_schema in data.get("generics", []): - generic = GenericSchemaAPI(**generic_schema) - nodes[generic.kind] = generic - - for profile_schema in data.get("profiles", []): - profile = ProfileSchemaAPI(**profile_schema) - nodes[profile.kind] = profile + response = await self.client._get(url=url) - for template_schema in data.get("templates", []): - template = TemplateSchemaAPI(**template_schema) - nodes[template.kind] = template + data = self._parse_schema_response(response=response, branch=branch) - schema_hash = data.get("main", "") - - return BranchSchema(hash=schema_hash, nodes=nodes) + return BranchSchema.from_api_response(data=data) class InfrahubSchemaSync(InfrahubSchemaBase): - def __init__(self, client: InfrahubClientSync): - self.client = client - self.cache: dict[str, BranchSchema] = {} + client: InfrahubClientSync def all( self, @@ -494,10 +527,25 @@ def get( refresh: bool = False, timeout: int | None = None, ) -> MainSchemaTypesAPI: + """ + Retrieve a specific schema object from the server. + + Args: + kind: The kind of schema object to retrieve. + branch: The branch to retrieve the schema from. + refresh: Whether to refresh the schema. + timeout: Overrides default timeout used when querying the GraphQL API. Specified in seconds (deprecated). + + Returns: + MainSchemaTypes: The schema object. + """ branch = branch or self.client.default_branch kind_str = self._get_schema_name(schema=kind) + if timeout: + self._deprecated_schema_timeout() + if refresh: self.cache[branch] = self._fetch(branch=branch) @@ -507,7 +555,7 @@ def get( # Fetching the latest schema from the server if we didn't fetch it earlier # because we coulnd't find the object on the local cache if not refresh: - self.cache[branch] = self._fetch(branch=branch, timeout=timeout) + self.cache[branch] = self._fetch(branch=branch) if branch in self.cache and kind_str in self.cache[branch].nodes: return self.cache[branch].nodes[kind_str] @@ -627,51 +675,39 @@ def add_dropdown_option( ) def fetch( - self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None + self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None, populate_cache: bool = True ) -> MutableMapping[str, MainSchemaTypesAPI]: """Fetch the schema from the server for a given branch. Args: - branch (str): Name of the branch to fetch the schema for. - timeout (int, optional): Overrides default timeout used when querying the GraphQL API. Specified in seconds. + branch: Name of the branch to fetch the schema for. + timeout: Overrides default timeout used when querying the GraphQL API. Specified in seconds (deprecated). + populate_cache: Whether to populate the cache with the fetched schema. Defaults to True. Returns: dict[str, MainSchemaTypes]: Dictionary of all schema organized by kind """ - branch_schema = self._fetch(branch=branch, namespaces=namespaces, timeout=timeout) + if timeout: + self._deprecated_schema_timeout() + + branch_schema = self._fetch(branch=branch, namespaces=namespaces) + + if populate_cache: + self.cache[branch] = branch_schema + return branch_schema.nodes - def _fetch(self, branch: str, namespaces: list[str] | None = None, timeout: int | None = None) -> BranchSchema: + def _fetch(self, branch: str, namespaces: list[str] | None = None) -> BranchSchema: url_parts = [("branch", branch)] if namespaces: url_parts.extend([("namespaces", ns) for ns in namespaces]) query_params = urlencode(url_parts) url = f"{self.client.address}/api/schema?{query_params}" - response = self.client._get(url=url, timeout=timeout) - response.raise_for_status() - - data: MutableMapping[str, Any] = response.json() - - nodes: MutableMapping[str, MainSchemaTypesAPI] = {} - for node_schema in data.get("nodes", []): - node = NodeSchemaAPI(**node_schema) - nodes[node.kind] = node - - for generic_schema in data.get("generics", []): - generic = GenericSchemaAPI(**generic_schema) - nodes[generic.kind] = generic - - for profile_schema in data.get("profiles", []): - profile = ProfileSchemaAPI(**profile_schema) - nodes[profile.kind] = profile - - for template_schema in data.get("templates", []): - template = TemplateSchemaAPI(**template_schema) - nodes[template.kind] = template + response = self.client._get(url=url) - schema_hash = data.get("main", "") + data = self._parse_schema_response(response=response, branch=branch) - return BranchSchema(hash=schema_hash, nodes=nodes) + return BranchSchema.from_api_response(data=data) def load( self, schemas: list[dict], branch: str | None = None, wait_until_converged: bool = False diff --git a/infrahub_sdk/schema/main.py b/infrahub_sdk/schema/main.py index af5556b3..e2985d7b 100644 --- a/infrahub_sdk/schema/main.py +++ b/infrahub_sdk/schema/main.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, Union from pydantic import BaseModel, ConfigDict, Field +from typing_extensions import Self if TYPE_CHECKING: from ..node import InfrahubNode, InfrahubNodeSync @@ -231,7 +232,11 @@ def mandatory_input_names(self) -> list[str]: @property def mandatory_attribute_names(self) -> list[str]: - return [item.name for item in self.attributes if not item.optional and item.default_value is None] + return [ + item.name + for item in self.attributes + if (not item.optional and item.default_value is None) and not item.read_only + ] @property def mandatory_relationship_names(self) -> list[str]: @@ -344,7 +349,7 @@ def to_schema_dict(self) -> dict[str, Any]: class SchemaRootAPI(BaseModel): model_config = ConfigDict(use_enum_values=True) - version: str + main: str | None = None generics: list[GenericSchemaAPI] = Field(default_factory=list) nodes: list[NodeSchemaAPI] = Field(default_factory=list) profiles: list[ProfileSchemaAPI] = Field(default_factory=list) @@ -356,3 +361,32 @@ class BranchSchema(BaseModel): nodes: MutableMapping[str, GenericSchemaAPI | NodeSchemaAPI | ProfileSchemaAPI | TemplateSchemaAPI] = Field( default_factory=dict ) + + @classmethod + def from_api_response(cls, data: MutableMapping[str, Any]) -> Self: + """ + Convert an API response from /api/schema into a BranchSchema object. + """ + return cls.from_schema_root_api(data=SchemaRootAPI(**data)) + + @classmethod + def from_schema_root_api(cls, data: SchemaRootAPI) -> Self: + """ + Convert a SchemaRootAPI object to a BranchSchema object. + """ + nodes: MutableMapping[str, GenericSchemaAPI | NodeSchemaAPI | ProfileSchemaAPI | TemplateSchemaAPI] = {} + for node in data.nodes: + nodes[node.kind] = node + + for generic in data.generics: + nodes[generic.kind] = generic + + for profile in data.profiles: + nodes[profile.kind] = profile + + for template in data.templates: + nodes[template.kind] = template + + schema_hash = data.main or "" + + return cls(hash=schema_hash, nodes=nodes) diff --git a/infrahub_sdk/schema/repository.py b/infrahub_sdk/schema/repository.py index 275a6ddc..1628fd6d 100644 --- a/infrahub_sdk/schema/repository.py +++ b/infrahub_sdk/schema/repository.py @@ -83,6 +83,7 @@ def load_class(self, import_root: str | None = None, relative_path: str | None = class InfrahubGeneratorDefinitionConfig(InfrahubRepositoryConfigElement): model_config = ConfigDict(extra="forbid") + name: str = Field(..., description="The name of the Generator Definition") file_path: Path = Field(..., description="The file within the repository with the generator code.") query: str = Field(..., description="The GraphQL query to use as input.") @@ -112,6 +113,7 @@ def load_class(self, import_root: str | None = None, relative_path: str | None = class InfrahubPythonTransformConfig(InfrahubRepositoryConfigElement): model_config = ConfigDict(extra="forbid") + name: str = Field(..., description="The name of the Transform") file_path: Path = Field(..., description="The file within the repository with the transform code.") class_name: str = Field(default="Transform", description="The name of the transform class to run.") diff --git a/infrahub_sdk/spec/menu.py b/infrahub_sdk/spec/menu.py index 87b73354..d49985f0 100644 --- a/infrahub_sdk/spec/menu.py +++ b/infrahub_sdk/spec/menu.py @@ -1,7 +1,7 @@ from __future__ import annotations from ..yaml import InfrahubFile, InfrahubFileKind -from .object import InfrahubObjectFileData +from .object import InfrahubObjectFileData, ObjectFile class InfrahubMenuFileData(InfrahubObjectFileData): @@ -18,7 +18,7 @@ def enrich_node(cls, data: dict, context: dict) -> dict: return data -class MenuFile(InfrahubFile): +class MenuFile(ObjectFile): _spec: InfrahubMenuFileData | None = None @property @@ -28,7 +28,7 @@ def spec(self) -> InfrahubMenuFileData: return self._spec def validate_content(self) -> None: - super().validate_content() + InfrahubFile.validate_content(self) if self.kind != InfrahubFileKind.MENU: raise ValueError("File is not an Infrahub Menu file") self._spec = InfrahubMenuFileData(**self.data.spec) diff --git a/infrahub_sdk/spec/object.py b/infrahub_sdk/spec/object.py index 1445a5eb..acbf1551 100644 --- a/infrahub_sdk/spec/object.py +++ b/infrahub_sdk/spec/object.py @@ -1,20 +1,363 @@ from __future__ import annotations +from enum import Enum from typing import TYPE_CHECKING, Any from pydantic import BaseModel, Field +from ..exceptions import ObjectValidationError, ValidationError +from ..schema import GenericSchemaAPI, RelationshipKind, RelationshipSchema from ..yaml import InfrahubFile, InfrahubFileKind if TYPE_CHECKING: from ..client import InfrahubClient - from ..schema import MainSchemaTypesAPI + from ..node import InfrahubNode + from ..schema import MainSchemaTypesAPI, RelationshipSchema + + +def validate_list_of_scalars(value: list[Any]) -> bool: + return all(isinstance(item, (str, int, float, bool)) for item in value) + + +def validate_list_of_hfids(value: list[Any]) -> bool: + return all(isinstance(item, (str, list)) for item in value) + + +def validate_list_of_data_dicts(value: list[Any]) -> bool: + return all(isinstance(item, dict) and "data" in item for item in value) + + +def validate_list_of_objects(value: list[Any]) -> bool: + return all(isinstance(item, dict) for item in value) + + +class RelationshipDataFormat(str, Enum): + UNKNOWN = "unknown" + + ONE_REF = "one_ref" + ONE_OBJ = "one_obj" + + MANY_OBJ_DICT_LIST = "many_obj_dict_list" + MANY_OBJ_LIST_DICT = "many_obj_list_dict" + MANY_REF = "many_ref_list" + + +class RelationshipInfo(BaseModel): + name: str + rel_schema: RelationshipSchema + peer_kind: str + peer_rel: RelationshipSchema | None = None + reason_relationship_not_valid: str | None = None + format: RelationshipDataFormat = RelationshipDataFormat.UNKNOWN + + @property + def is_bidirectional(self) -> bool: + """Indicate if a relationship with the same identifier exists on the other side""" + return bool(self.peer_rel) + + @property + def is_mandatory(self) -> bool: + if not self.peer_rel: + return False + # For hierarchical node, currently the relationship to the parent is always optional in the schema even if it's mandatory + # In order to build the tree from top to bottom, we need to consider it as mandatory + # While it should technically work bottom-up, it created some unexpected behavior while loading the menu + if self.peer_rel.cardinality == "one" and self.peer_rel.kind == RelationshipKind.HIERARCHY: + return True + return not self.peer_rel.optional + + @property + def is_valid(self) -> bool: + return not self.reason_relationship_not_valid + + @property + def is_reference(self) -> bool: + return self.format in [RelationshipDataFormat.ONE_REF, RelationshipDataFormat.MANY_REF] + + def get_context(self, value: Any) -> dict: + """Return a dict to insert to the context if the relationship is mandatory""" + if self.peer_rel and self.is_mandatory and self.peer_rel.cardinality == "one": + return {self.peer_rel.name: value} + if self.peer_rel and self.is_mandatory and self.peer_rel.cardinality == "many": + return {self.peer_rel.name: [value]} + return {} + + def find_matching_relationship( + self, peer_schema: MainSchemaTypesAPI, force: bool = False + ) -> RelationshipSchema | None: + """Find the matching relationship on the other side of the relationship""" + if self.peer_rel and not force: + return self.peer_rel + + try: + self.peer_rel = peer_schema.get_matching_relationship( + id=self.rel_schema.identifier or "", direction=self.rel_schema.direction + ) + except ValueError: + pass + + return self.peer_rel + + +async def get_relationship_info( + client: InfrahubClient, schema: MainSchemaTypesAPI, name: str, value: Any, branch: str | None = None +) -> RelationshipInfo: + """ + Get the relationship info for a given relationship name. + """ + rel_schema = schema.get_relationship(name=name) + + info = RelationshipInfo(name=name, peer_kind=rel_schema.peer, rel_schema=rel_schema) + + if isinstance(value, dict) and "data" not in value: + info.reason_relationship_not_valid = f"Relationship {name} must be a dict with 'data'" + return info + + if isinstance(value, dict) and "kind" in value: + info.peer_kind = value["kind"] + + peer_schema = await client.schema.get(kind=info.peer_kind, branch=branch) + + try: + info.peer_rel = peer_schema.get_matching_relationship( + id=rel_schema.identifier or "", direction=rel_schema.direction + ) + except ValueError: + pass + + if rel_schema.cardinality == "one" and isinstance(value, list): + # validate the list is composed of string + if validate_list_of_scalars(value): + info.format = RelationshipDataFormat.ONE_REF + else: + info.reason_relationship_not_valid = "Too many objects provided for a relationship of cardinality one" + + elif rel_schema.cardinality == "one" and isinstance(value, str): + info.format = RelationshipDataFormat.ONE_REF + + elif rel_schema.cardinality == "one" and isinstance(value, dict) and "data" in value: + info.format = RelationshipDataFormat.ONE_OBJ + + elif ( + rel_schema.cardinality == "many" + and isinstance(value, dict) + and "data" in value + and validate_list_of_objects(value["data"]) + ): + # Initial format, we need to support it for backward compatibility for menu + # it's helpful if there is only one type of object to manage + info.format = RelationshipDataFormat.MANY_OBJ_DICT_LIST + + elif rel_schema.cardinality == "many" and isinstance(value, dict) and "data" not in value: + info.reason_relationship_not_valid = "Invalid structure for a relationship of cardinality many," + " either provide a dict with data as a list or a list of objects" + + elif rel_schema.cardinality == "many" and isinstance(value, list): + if validate_list_of_data_dicts(value): + info.format = RelationshipDataFormat.MANY_OBJ_LIST_DICT + elif validate_list_of_hfids(value): + info.format = RelationshipDataFormat.MANY_REF + else: + info.reason_relationship_not_valid = "Invalid structure for a relationship of cardinality many," + " either provide a list of dict with data or a list of hfids" + + return info class InfrahubObjectFileData(BaseModel): kind: str data: list[dict[str, Any]] = Field(default_factory=list) + async def validate_format(self, client: InfrahubClient, branch: str | None = None) -> list[ObjectValidationError]: + errors: list[ObjectValidationError] = [] + schema = await client.schema.get(kind=self.kind, branch=branch) + for idx, item in enumerate(self.data): + errors.extend( + await self.validate_object( + client=client, + position=[idx + 1], + schema=schema, + data=item, + branch=branch, + default_schema_kind=self.kind, + ) + ) + return errors + + async def process(self, client: InfrahubClient, branch: str | None = None) -> None: + schema = await client.schema.get(kind=self.kind, branch=branch) + for idx, item in enumerate(self.data): + await self.create_node( + client=client, + schema=schema, + data=item, + position=[idx + 1], + branch=branch, + default_schema_kind=self.kind, + ) + + @classmethod + async def validate_object( + cls, + client: InfrahubClient, + schema: MainSchemaTypesAPI, + data: dict, + position: list[int | str], + context: dict | None = None, + branch: str | None = None, + default_schema_kind: str | None = None, + ) -> list[ObjectValidationError]: + errors: list[ObjectValidationError] = [] + context = context.copy() if context else {} + + # First validate if all mandatory fields are present + for element in schema.mandatory_input_names: + if not any([element in data.keys(), element in context.keys()]): + errors.append(ObjectValidationError(position=position + [element], message=f"{element} is mandatory")) + + # Validate if all attributes are valid + for key, value in data.items(): + if key not in schema.attribute_names and key not in schema.relationship_names: + errors.append( + ObjectValidationError( + position=position + [key], + message=f"{key} is not a valid attribute or relationship for {schema.kind}", + ) + ) + + if key in schema.attribute_names: + if not isinstance(value, (str, int, float, bool, list, dict)): + errors.append( + ObjectValidationError( + position=position + [key], + message=f"{key} must be a string, int, float, bool, list, or dict", + ) + ) + + if key in schema.relationship_names: + rel_info = await get_relationship_info( + client=client, schema=schema, name=key, value=value, branch=branch + ) + if not rel_info.is_valid: + errors.append( + ObjectValidationError( + position=position + [key], + message=rel_info.reason_relationship_not_valid or "Invalid relationship", + ) + ) + + errors.extend( + await cls.validate_related_nodes( + client=client, + position=position + [key], + rel_info=rel_info, + data=value, + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + ) + + return errors + + @classmethod + async def validate_related_nodes( + cls, + client: InfrahubClient, + position: list[int | str], + rel_info: RelationshipInfo, + data: dict | list[dict], + context: dict | None = None, + branch: str | None = None, + default_schema_kind: str | None = None, + ) -> list[ObjectValidationError]: + context = context.copy() if context else {} + errors: list[ObjectValidationError] = [] + + if isinstance(data, (list, str)) and rel_info.format == RelationshipDataFormat.ONE_REF: + return errors + + if isinstance(data, list) and rel_info.format == RelationshipDataFormat.MANY_REF: + return errors + + if isinstance(data, dict) and rel_info.format == RelationshipDataFormat.ONE_OBJ: + peer_kind = data.get("kind") or rel_info.peer_kind + peer_schema = await cls.get_peer_schema( + client=client, peer_kind=peer_kind, branch=branch, default_schema_kind=default_schema_kind + ) + + rel_info.find_matching_relationship(peer_schema=peer_schema) + context.update(rel_info.get_context(value="placeholder")) + + errors.extend( + await cls.validate_object( + client=client, + position=position, + schema=peer_schema, + data=data["data"], + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + ) + return errors + + if isinstance(data, dict) and rel_info.format == RelationshipDataFormat.MANY_OBJ_DICT_LIST: + peer_kind = data.get("kind") or rel_info.peer_kind + peer_schema = await cls.get_peer_schema( + client=client, peer_kind=peer_kind, branch=branch, default_schema_kind=default_schema_kind + ) + + rel_info.find_matching_relationship(peer_schema=peer_schema) + context.update(rel_info.get_context(value="placeholder")) + + for idx, peer_data in enumerate(data["data"]): + context["list_index"] = idx + errors.extend( + await cls.validate_object( + client=client, + position=position + [idx + 1], + schema=peer_schema, + data=peer_data, + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + ) + return errors + + if isinstance(data, list) and rel_info.format == RelationshipDataFormat.MANY_OBJ_LIST_DICT: + for idx, item in enumerate(data): + context["list_index"] = idx + peer_kind = item.get("kind") or rel_info.peer_kind + peer_schema = await cls.get_peer_schema( + client=client, peer_kind=peer_kind, branch=branch, default_schema_kind=default_schema_kind + ) + + rel_info.find_matching_relationship(peer_schema=peer_schema) + context.update(rel_info.get_context(value="placeholder")) + + errors.extend( + await cls.validate_object( + client=client, + position=position + [idx + 1], + schema=peer_schema, + data=item["data"], + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + ) + return errors + + errors.append( + ObjectValidationError( + position=position, + message=f"Relationship {rel_info.rel_schema.name} doesn't have the right format {rel_info.rel_schema.cardinality} / {type(data)}", + ) + ) + return errors + @classmethod def enrich_node(cls, data: dict, context: dict) -> dict: # noqa: ARG003 return data @@ -25,35 +368,82 @@ async def create_node( client: InfrahubClient, schema: MainSchemaTypesAPI, data: dict, + position: list[int | str], context: dict | None = None, branch: str | None = None, default_schema_kind: str | None = None, - ) -> None: - # First validate of all mandatory fields are present - for element in schema.mandatory_attribute_names + schema.mandatory_relationship_names: - if element not in data.keys(): - raise ValueError(f"{element} is mandatory") + ) -> InfrahubNode: + context = context.copy() if context else {} + + errors = await cls.validate_object( + client=client, + position=position, + schema=schema, + data=data, + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + if errors: + messages = [str(error) for error in errors] + raise ObjectValidationError(position=position, message="Object is not valid - " + ", ".join(messages)) clean_data: dict[str, Any] = {} + # List of relationships that need to be processed after the current object has been created remaining_rels = [] + rels_info: dict[str, RelationshipInfo] = {} + for key, value in data.items(): if key in schema.attribute_names: clean_data[key] = value + continue if key in schema.relationship_names: - rel_schema = schema.get_relationship(name=key) + rel_info = await get_relationship_info( + client=client, schema=schema, name=key, value=value, branch=branch + ) + rels_info[key] = rel_info - if isinstance(value, dict) and "data" not in value: - raise ValueError(f"Relationship {key} must be a dict with 'data'") + if not rel_info.is_valid: + client.log.info(rel_info.reason_relationship_not_valid) + continue - # This is a simple implementation for now, need to revisit once we have the integration tests - if isinstance(value, (list)): + # We need to determine if the related object depend on this object or if this is the other way around. + # - if the relationship is bidirectional and is mandatory on the other side, then we need to create this object First + # - if the relationship is bidirectional and is not mandatory on the other side, then we need should create the related object First + # - if the relationship is not bidirectional, then we need to create the related object First + if rel_info.is_reference and isinstance(value, list): clean_data[key] = value - elif rel_schema.cardinality == "one" and isinstance(value, str): + elif rel_info.format == RelationshipDataFormat.ONE_REF and isinstance(value, str): clean_data[key] = [value] - else: + elif not rel_info.is_reference and rel_info.is_bidirectional and rel_info.is_mandatory: remaining_rels.append(key) + elif not rel_info.is_reference and not rel_info.is_mandatory: + if rel_info.format == RelationshipDataFormat.ONE_OBJ: + nodes = await cls.create_related_nodes( + client=client, + position=position, + rel_info=rel_info, + data=value, + branch=branch, + default_schema_kind=default_schema_kind, + ) + clean_data[key] = nodes[0] + + else: + nodes = await cls.create_related_nodes( + client=client, + position=position, + rel_info=rel_info, + data=value, + branch=branch, + default_schema_kind=default_schema_kind, + ) + clean_data[key] = nodes + + else: + raise ValueError(f"Situation unaccounted for: {rel_info}") if context: clean_context = { @@ -67,54 +457,136 @@ async def create_node( node = await client.create(kind=schema.kind, branch=branch, data=clean_data) await node.save(allow_upsert=True) + display_label = node.get_human_friendly_id_as_string() or f"{node.get_kind()} : {node.id}" client.log.info(f"Node: {display_label}") for rel in remaining_rels: - # identify what is the name of the relationship on the other side - if not isinstance(data[rel], dict) and "data" in data[rel]: - raise ValueError(f"relationship {rel} must be a dict with 'data'") + context = {} + + # If there is a peer relationship, we add the node id to the context + rel_info = rels_info[rel] + context.update(rel_info.get_context(value=node.id)) + + await cls.create_related_nodes( + client=client, + parent_node=node, + rel_info=rel_info, + position=position, + data=data[rel], + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + + return node + + @classmethod + async def create_related_nodes( + cls, + client: InfrahubClient, + rel_info: RelationshipInfo, + position: list[int | str], + data: dict | list[dict], + parent_node: InfrahubNode | None = None, + context: dict | None = None, + branch: str | None = None, + default_schema_kind: str | None = None, + ) -> list[InfrahubNode]: + nodes: list[InfrahubNode] = [] + context = context.copy() if context else {} - rel_schema = schema.get_relationship(name=rel) - peer_kind = data[rel].get("kind", default_schema_kind) or rel_schema.peer + if isinstance(data, dict) and rel_info.format == RelationshipDataFormat.ONE_OBJ: + peer_kind = data.get("kind") or rel_info.peer_kind peer_schema = await client.schema.get(kind=peer_kind, branch=branch) - if rel_schema.identifier is None: - raise ValueError("identifier must be defined") + if parent_node: + rel_info.find_matching_relationship(peer_schema=peer_schema) + context.update(rel_info.get_context(value=parent_node.id)) - peer_rel = peer_schema.get_matching_relationship(id=rel_schema.identifier, direction=rel_schema.direction) + new_node = await cls.create_node( + client=client, + schema=peer_schema, + position=position, + data=data["data"], + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + return [new_node] - rel_data = data[rel]["data"] - context = {} - if peer_rel: - context[peer_rel.name] = node.id + if isinstance(data, dict) and rel_info.format == RelationshipDataFormat.MANY_OBJ_DICT_LIST: + peer_kind = data.get("kind") or rel_info.peer_kind + peer_schema = await cls.get_peer_schema( + client=client, peer_kind=peer_kind, branch=branch, default_schema_kind=default_schema_kind + ) - if rel_schema.cardinality == "one" and isinstance(rel_data, dict): - await cls.create_node( - client=client, - schema=peer_schema, - data=rel_data, - context=context, - branch=branch, - default_schema_kind=default_schema_kind, - ) + if parent_node: + rel_info.find_matching_relationship(peer_schema=peer_schema) + context.update(rel_info.get_context(value=parent_node.id)) - elif rel_schema.cardinality == "many" and isinstance(rel_data, list): - for idx, peer_data in enumerate(rel_data): - context["list_index"] = idx - await cls.create_node( + for idx, peer_data in enumerate(data["data"]): + context["list_index"] = idx + if isinstance(peer_data, dict): + node = await cls.create_node( client=client, schema=peer_schema, + position=position + [rel_info.name, idx + 1], data=peer_data, context=context, branch=branch, default_schema_kind=default_schema_kind, ) - else: - raise ValueError( - f"Relationship {rel_schema.name} doesn't have the right format {rel_schema.cardinality} / {type(rel_data)}" + nodes.append(node) + return nodes + + if isinstance(data, list) and rel_info.format == RelationshipDataFormat.MANY_OBJ_LIST_DICT: + for idx, item in enumerate(data): + context["list_index"] = idx + + peer_kind = item.get("kind") or rel_info.peer_kind + peer_schema = await cls.get_peer_schema( + client=client, peer_kind=peer_kind, branch=branch, default_schema_kind=default_schema_kind ) + if parent_node: + rel_info.find_matching_relationship(peer_schema=peer_schema) + context.update(rel_info.get_context(value=parent_node.id)) + + node = await cls.create_node( + client=client, + schema=peer_schema, + position=position + [rel_info.name, idx + 1], + data=item["data"], + context=context, + branch=branch, + default_schema_kind=default_schema_kind, + ) + nodes.append(node) + + return nodes + + raise ValueError( + f"Relationship {rel_info.rel_schema.name} doesn't have the right format {rel_info.rel_schema.cardinality} / {type(data)}" + ) + + @classmethod + async def get_peer_schema( + cls, client: InfrahubClient, peer_kind: str, branch: str | None = None, default_schema_kind: str | None = None + ) -> MainSchemaTypesAPI: + peer_schema = await client.schema.get(kind=peer_kind, branch=branch) + if not isinstance(peer_schema, GenericSchemaAPI): + return peer_schema + + if not default_schema_kind: + raise ValueError(f"Found a peer schema as a generic {peer_kind} but no default value was provided") + + # if the initial peer_kind was a generic, we try the default_schema_kind + peer_schema = await client.schema.get(kind=default_schema_kind, branch=branch) + if isinstance(peer_schema, GenericSchemaAPI): + raise ValueError(f"Default schema kind {default_schema_kind} can't be a generic") + return peer_schema + class ObjectFile(InfrahubFile): _spec: InfrahubObjectFileData | None = None @@ -130,3 +602,12 @@ def validate_content(self) -> None: if self.kind != InfrahubFileKind.OBJECT: raise ValueError("File is not an Infrahub Object file") self._spec = InfrahubObjectFileData(**self.data.spec) + + async def validate_format(self, client: InfrahubClient, branch: str | None = None) -> None: + self.validate_content() + errors = await self.spec.validate_format(client=client, branch=branch) + if errors: + raise ValidationError(identifier=str(self.location), messages=[str(error) for error in errors]) + + async def process(self, client: InfrahubClient, branch: str | None = None) -> None: + await self.spec.process(client=client, branch=branch) diff --git a/infrahub_sdk/testing/docker.py b/infrahub_sdk/testing/docker.py index e5865678..0f7d46b2 100644 --- a/infrahub_sdk/testing/docker.py +++ b/infrahub_sdk/testing/docker.py @@ -8,13 +8,16 @@ from .. import Config, InfrahubClient, InfrahubClientSync -INFRAHUB_VERSION = os.getenv("INFRAHUB_TESTING_IMAGE_VER", "latest") +INFRAHUB_VERSION = os.getenv("INFRAHUB_TESTING_IMAGE_VER") def skip_version(min_infrahub_version: str | None = None, max_infrahub_version: str | None = None) -> bool: """ Check if a test should be skipped depending on infrahub version. """ + if INFRAHUB_VERSION is None: + return True + try: version = Version(INFRAHUB_VERSION) except InvalidVersion: @@ -31,10 +34,6 @@ def skip_version(min_infrahub_version: str | None = None, max_infrahub_version: class TestInfrahubDockerClient(TestInfrahubDocker): - @pytest.fixture(scope="class") - def infrahub_version(self) -> str: - return INFRAHUB_VERSION - @pytest.fixture(scope="class") def client(self, infrahub_port: int) -> InfrahubClient: return InfrahubClient( diff --git a/infrahub_sdk/testing/schemas/animal.py b/infrahub_sdk/testing/schemas/animal.py index b0e288d3..559e75d4 100644 --- a/infrahub_sdk/testing/schemas/animal.py +++ b/infrahub_sdk/testing/schemas/animal.py @@ -20,6 +20,7 @@ TESTING_CAT = f"{NAMESPACE}Cat" TESTING_DOG = f"{NAMESPACE}Dog" TESTING_PERSON = f"{NAMESPACE}Person" +BUILTIN_TAG = "BuiltinTag" class SchemaAnimal: @@ -125,6 +126,12 @@ def schema_person(self) -> NodeSchema: cardinality="many", direction=RelationshipDirection.INBOUND, ), + Rel( + name="tags", + optional=True, + peer=BUILTIN_TAG, + cardinality="many", + ), ], ) diff --git a/infrahub_sdk/yaml.py b/infrahub_sdk/yaml.py index 69d973cf..7a0a3334 100644 --- a/infrahub_sdk/yaml.py +++ b/infrahub_sdk/yaml.py @@ -7,6 +7,7 @@ import yaml from pydantic import BaseModel, Field from typing_extensions import Self +from yaml.parser import ParserError from .exceptions import FileNotValidError from .utils import find_files, read_file @@ -25,12 +26,14 @@ class InfrahubFileData(BaseModel): api_version: InfrahubFileApiVersion = Field(InfrahubFileApiVersion.V1, alias="apiVersion") kind: InfrahubFileKind spec: dict - metadata: dict | None = Field(default_factory=dict) + metadata: dict = Field(default_factory=dict) class LocalFile(BaseModel): identifier: str | None = None location: Path + multiple_documents: bool = False + document_position: int | None = None content: dict | None = None valid: bool = True error_message: str | None = None @@ -57,20 +60,73 @@ def load_content(self) -> None: def validate_content(self) -> None: pass + @classmethod + def init(cls, location: Path, multiple_documents: bool, content: dict | None) -> Self: + if not content: + return cls._file_is_empty(path=location, has_multiple_document=multiple_documents) + + return cls(location=location, multiple_documents=multiple_documents, content=content) + + @classmethod + def _file_is_empty(cls, path: Path, has_multiple_document: bool) -> Self: + return cls( + location=path, multiple_documents=has_multiple_document, error_message="Invalid YAML/JSON file", valid=False + ) + + @classmethod + def _file_is_invalid(cls, path: Path, has_multiple_document: bool) -> Self: + return cls( + location=path, + multiple_documents=has_multiple_document, + error_message="Invalid YAML/JSON file", + valid=False, + ) + + @classmethod + def load_file_from_disk(cls, path: Path) -> list[Self]: + yaml_files: list[Self] = [] + + try: + file_content = read_file(path) + + has_multiple_document = bool(file_content.count("---") > 1) + + if has_multiple_document: + for content in yaml.safe_load_all(file_content): + yaml_files.append( + cls.init(location=path, multiple_documents=has_multiple_document, content=content) + ) + + else: + yaml_files.append( + cls.init( + location=path, multiple_documents=has_multiple_document, content=yaml.safe_load(file_content) + ) + ) + + except FileNotValidError as exc: + yaml_files.append( + cls(location=path, multiple_documents=has_multiple_document, error_message=exc.message, valid=False) + ) + except (yaml.YAMLError, ParserError): + yaml_files.append(cls._file_is_invalid(path, has_multiple_document)) + + if has_multiple_document: + for idx, file in enumerate(yaml_files): + file.document_position = idx + 1 + + return yaml_files + @classmethod def load_from_disk(cls, paths: list[Path]) -> list[Self]: yaml_files: list[Self] = [] for file_path in paths: if file_path.is_file(): - yaml_file = cls(location=file_path) - yaml_file.load_content() - yaml_files.append(yaml_file) + yaml_files.extend(cls.load_file_from_disk(path=file_path)) elif file_path.is_dir(): files = find_files(extension=["yaml", "yml", "json"], directory=file_path) for item in files: - yaml_file = cls(location=item) - yaml_file.load_content() - yaml_files.append(yaml_file) + yaml_files.extend(cls.load_file_from_disk(path=item)) else: raise FileNotValidError(name=str(file_path), message=f"{file_path} does not exist!") diff --git a/poetry.lock b/poetry.lock index 9938fd39..ac79c770 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -341,6 +341,7 @@ description = "A Python library for the Docker Engine API." optional = false python-versions = ">=3.8" groups = ["dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, @@ -572,14 +573,14 @@ trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httpx" -version = "0.27.2" +version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, - {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, ] [package.dependencies] @@ -587,7 +588,6 @@ anyio = "*" certifi = "*" httpcore = "==1.*" idna = "*" -sniffio = "*" [package.extras] brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] @@ -677,17 +677,21 @@ type = ["pytest-mypy"] [[package]] name = "infrahub-testcontainers" -version = "1.1.0b2" +version = "1.2.5" description = "Testcontainers instance for Infrahub to easily build integration tests" optional = false python-versions = "<4.0,>=3.9" groups = ["dev"] +markers = "python_version >= \"3.10\"" files = [ - {file = "infrahub_testcontainers-1.1.0b2-py3-none-any.whl", hash = "sha256:40a4f735b988db0f20eeedc68eab2fc40dcfba37382d9836a49bd6dbc282b80a"}, - {file = "infrahub_testcontainers-1.1.0b2.tar.gz", hash = "sha256:fd3738a8f6588c16a8d88944b8f0c9faaa3a9f390cd2817bdabc8e08d4dae6a6"}, + {file = "infrahub_testcontainers-1.2.5-py3-none-any.whl", hash = "sha256:80b8c2e88cd75830365c7a1135d13e33c7c0f284c54f7974b10574e4bfef5134"}, + {file = "infrahub_testcontainers-1.2.5.tar.gz", hash = "sha256:447cc513c4208a3eac8fc3e480c57d4895c9fe3b167357a83dcadd55821f7e5e"}, ] [package.dependencies] +httpx = ">=0.28.1,<0.29.0" +psutil = "*" +pydantic = ">=2.10.6,<3.0.0" pytest = "*" testcontainers = ">=4.8,<4.9" @@ -983,6 +987,7 @@ version = "1.12.0" description = "Common helper functions useful in network automation." optional = false python-versions = "<4.0,>=3.8" +groups = ["main"] files = [ {file = "netutils-1.12.0-py3-none-any.whl", hash = "sha256:7cb37796ce86637814f8c899f64db2b054986b0eda719d3fcadc293d451a4db1"}, {file = "netutils-1.12.0.tar.gz", hash = "sha256:96a790d11921063a6a64ee79c6e8c5a5ffcd05cbee07dd2b614d98c4416cffdd"}, @@ -1185,6 +1190,31 @@ files = [ [package.dependencies] wcwidth = "*" +[[package]] +name = "psutil" +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, +] + +[package.extras] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + [[package]] name = "ptyprocess" version = "0.7.0" @@ -1271,125 +1301,133 @@ test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] [[package]] name = "pydantic" -version = "2.9.1" +version = "2.11.1" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"}, - {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"}, + {file = "pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8"}, + {file = "pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.3" -typing-extensions = [ - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, -] +pydantic-core = "2.33.0" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and sys_platform == \"win32\""] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" -version = "2.23.3" +version = "2.33.0" description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"}, - {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"}, - {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"}, - {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"}, - {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"}, - {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"}, - {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"}, - {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"}, - {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"}, - {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"}, - {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"}, - {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"}, - {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"}, - {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"}, - {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"}, - {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"}, - {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"}, - {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"}, - {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"}, - {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"}, - {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"}, - {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"}, - {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"}, - {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"}, - {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"}, - {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"}, - {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"}, - {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"}, - {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"}, - {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"}, - {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"}, - {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"}, - {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"}, + {file = "pydantic_core-2.33.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71dffba8fe9ddff628c68f3abd845e91b028361d43c5f8e7b3f8b91d7d85413e"}, + {file = "pydantic_core-2.33.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:abaeec1be6ed535a5d7ffc2e6c390083c425832b20efd621562fbb5bff6dc518"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759871f00e26ad3709efc773ac37b4d571de065f9dfb1778012908bcc36b3a73"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcfebee69cd5e1c0b76a17e17e347c84b00acebb8dd8edb22d4a03e88e82a207"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b1262b912435a501fa04cd213720609e2cefa723a07c92017d18693e69bf00b"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4726f1f3f42d6a25678c67da3f0b10f148f5655813c5aca54b0d1742ba821b8f"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e790954b5093dff1e3a9a2523fddc4e79722d6f07993b4cd5547825c3cbf97b5"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34e7fb3abe375b5c4e64fab75733d605dda0f59827752debc99c17cb2d5f3276"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ecb158fb9b9091b515213bed3061eb7deb1d3b4e02327c27a0ea714ff46b0760"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:4d9149e7528af8bbd76cc055967e6e04617dcb2a2afdaa3dea899406c5521faa"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e81a295adccf73477220e15ff79235ca9dcbcee4be459eb9d4ce9a2763b8386c"}, + {file = "pydantic_core-2.33.0-cp310-cp310-win32.whl", hash = "sha256:f22dab23cdbce2005f26a8f0c71698457861f97fc6318c75814a50c75e87d025"}, + {file = "pydantic_core-2.33.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cb2390355ba084c1ad49485d18449b4242da344dea3e0fe10babd1f0db7dcfc"}, + {file = "pydantic_core-2.33.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a608a75846804271cf9c83e40bbb4dab2ac614d33c6fd5b0c6187f53f5c593ef"}, + {file = "pydantic_core-2.33.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1c69aa459f5609dec2fa0652d495353accf3eda5bdb18782bc5a2ae45c9273a"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9ec80eb5a5f45a2211793f1c4aeddff0c3761d1c70d684965c1807e923a588b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e925819a98318d17251776bd3d6aa9f3ff77b965762155bdad15d1a9265c4cfd"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bf68bb859799e9cec3d9dd8323c40c00a254aabb56fe08f907e437005932f2b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b2ea72dea0825949a045fa4071f6d5b3d7620d2a208335207793cf29c5a182d"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1583539533160186ac546b49f5cde9ffc928062c96920f58bd95de32ffd7bffd"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23c3e77bf8a7317612e5c26a3b084c7edeb9552d645742a54a5867635b4f2453"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7a7f2a3f628d2f7ef11cb6188bcf0b9e1558151d511b974dfea10a49afe192b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:f1fb026c575e16f673c61c7b86144517705865173f3d0907040ac30c4f9f5915"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:635702b2fed997e0ac256b2cfbdb4dd0bf7c56b5d8fba8ef03489c03b3eb40e2"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win32.whl", hash = "sha256:07b4ced28fccae3f00626eaa0c4001aa9ec140a29501770a88dbbb0966019a86"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win_amd64.whl", hash = "sha256:4927564be53239a87770a5f86bdc272b8d1fbb87ab7783ad70255b4ab01aa25b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win_arm64.whl", hash = "sha256:69297418ad644d521ea3e1aa2e14a2a422726167e9ad22b89e8f1130d68e1e9a"}, + {file = "pydantic_core-2.33.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c32a40712e3662bebe524abe8abb757f2fa2000028d64cc5a1006016c06af43"}, + {file = "pydantic_core-2.33.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ec86b5baa36f0a0bfb37db86c7d52652f8e8aa076ab745ef7725784183c3fdd"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4deac83a8cc1d09e40683be0bc6d1fa4cde8df0a9bf0cda5693f9b0569ac01b6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:175ab598fb457a9aee63206a1993874badf3ed9a456e0654273e56f00747bbd6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f36afd0d56a6c42cf4e8465b6441cf546ed69d3a4ec92724cc9c8c61bd6ecf4"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a98257451164666afafc7cbf5fb00d613e33f7e7ebb322fbcd99345695a9a61"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecc6d02d69b54a2eb83ebcc6f29df04957f734bcf309d346b4f83354d8376862"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a69b7596c6603afd049ce7f3835bcf57dd3892fc7279f0ddf987bebed8caa5a"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea30239c148b6ef41364c6f51d103c2988965b643d62e10b233b5efdca8c0099"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:abfa44cf2f7f7d7a199be6c6ec141c9024063205545aa09304349781b9a125e6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20d4275f3c4659d92048c70797e5fdc396c6e4446caf517ba5cad2db60cd39d3"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win32.whl", hash = "sha256:918f2013d7eadea1d88d1a35fd4a1e16aaf90343eb446f91cb091ce7f9b431a2"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win_amd64.whl", hash = "sha256:aec79acc183865bad120b0190afac467c20b15289050648b876b07777e67ea48"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win_arm64.whl", hash = "sha256:5461934e895968655225dfa8b3be79e7e927e95d4bd6c2d40edd2fa7052e71b6"}, + {file = "pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555"}, + {file = "pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7"}, + {file = "pydantic_core-2.33.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7c9c84749f5787781c1c45bb99f433402e484e515b40675a5d121ea14711cf61"}, + {file = "pydantic_core-2.33.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:64672fa888595a959cfeff957a654e947e65bbe1d7d82f550417cbd6898a1d6b"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bc7367c0961dec292244ef2549afa396e72e28cc24706210bd44d947582c59"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce72d46eb201ca43994303025bd54d8a35a3fc2a3495fac653d6eb7205ce04f4"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14229c1504287533dbf6b1fc56f752ce2b4e9694022ae7509631ce346158de11"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:085d8985b1c1e48ef271e98a658f562f29d89bda98bf120502283efbc87313eb"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31860fbda80d8f6828e84b4a4d129fd9c4535996b8249cfb8c720dc2a1a00bb8"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f200b2f20856b5a6c3a35f0d4e344019f805e363416e609e9b47c552d35fd5ea"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f72914cfd1d0176e58ddc05c7a47674ef4222c8253bf70322923e73e14a4ac3"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:91301a0980a1d4530d4ba7e6a739ca1a6b31341252cb709948e0aca0860ce0ae"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7419241e17c7fbe5074ba79143d5523270e04f86f1b3a0dff8df490f84c8273a"}, + {file = "pydantic_core-2.33.0-cp39-cp39-win32.whl", hash = "sha256:7a25493320203005d2a4dac76d1b7d953cb49bce6d459d9ae38e30dd9f29bc9c"}, + {file = "pydantic_core-2.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:82a4eba92b7ca8af1b7d5ef5f3d9647eee94d1f74d21ca7c21e3a2b92e008358"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e2762c568596332fdab56b07060c8ab8362c56cf2a339ee54e491cd503612c50"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bf637300ff35d4f59c006fff201c510b2b5e745b07125458a5389af3c0dff8c"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c151ce3d59ed56ebd7ce9ce5986a409a85db697d25fc232f8e81f195aa39a1"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee65f0cc652261744fd07f2c6e6901c914aa6c5ff4dcfaf1136bc394d0dd26b"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:024d136ae44d233e6322027bbf356712b3940bee816e6c948ce4b90f18471b3d"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e37f10f6d4bc67c58fbd727108ae1d8b92b397355e68519f1e4a7babb1473442"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:502ed542e0d958bd12e7c3e9a015bce57deaf50eaa8c2e1c439b512cb9db1e3a"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:715c62af74c236bf386825c0fdfa08d092ab0f191eb5b4580d11c3189af9d330"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bccc06fa0372151f37f6b69834181aa9eb57cf8665ed36405fb45fbf6cac3bae"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d8dc9f63a26f7259b57f46a7aab5af86b2ad6fbe48487500bb1f4b27e051e4c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30369e54d6d0113d2aa5aee7a90d17f225c13d87902ace8fcd7bbf99b19124db"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb479354c62067afa62f53bb387827bee2f75c9c79ef25eef6ab84d4b1ae3b"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0310524c833d91403c960b8a3cf9f46c282eadd6afd276c8c5edc617bd705dc9"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eddb18a00bbb855325db27b4c2a89a4ba491cd6a0bd6d852b225172a1f54b36c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ade5dbcf8d9ef8f4b28e682d0b29f3008df9842bb5ac48ac2c17bc55771cc976"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2c0afd34f928383e3fd25740f2050dbac9d077e7ba5adbaa2227f4d4f3c8da5c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7da333f21cd9df51d5731513a6d39319892947604924ddf2e24a4612975fb936"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b6d77c75a57f041c5ee915ff0b0bb58eabb78728b69ed967bc5b780e8f701b8"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba95691cf25f63df53c1d342413b41bd7762d9acb425df8858d7efa616c0870e"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f1ab031feb8676f6bd7c85abec86e2935850bf19b84432c64e3e239bffeb1ec"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c1151827eef98b83d49b6ca6065575876a02d2211f259fb1a6b7757bd24dd8"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66d931ea2c1464b738ace44b7334ab32a2fd50be023d863935eb00f42be1778"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bcf0bab28995d483f6c8d7db25e0d05c3efa5cebfd7f56474359e7137f39856"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:89670d7a0045acb52be0566df5bc8b114ac967c662c06cf5e0c606e4aadc964b"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:b716294e721d8060908dbebe32639b01bfe61b15f9f57bcc18ca9a0e00d9520b"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fc53e05c16697ff0c1c7c2b98e45e131d4bfb78068fffff92a82d169cbb4c7b7"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:68504959253303d3ae9406b634997a2123a0b0c1da86459abbd0ffc921695eac"}, + {file = "pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3"}, ] [package.dependencies] @@ -1511,22 +1549,22 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-httpx" -version = "0.30.0" +version = "0.35.0" description = "Send responses to httpx." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-httpx-0.30.0.tar.gz", hash = "sha256:755b8edca87c974dd4f3605c374fda11db84631de3d163b99c0df5807023a19a"}, - {file = "pytest_httpx-0.30.0-py3-none-any.whl", hash = "sha256:6d47849691faf11d2532565d0c8e0e02b9f4ee730da31687feae315581d7520c"}, + {file = "pytest_httpx-0.35.0-py3-none-any.whl", hash = "sha256:ee11a00ffcea94a5cbff47af2114d34c5b231c326902458deed73f9c459fd744"}, + {file = "pytest_httpx-0.35.0.tar.gz", hash = "sha256:d619ad5d2e67734abfbb224c3d9025d64795d4b8711116b1a13f72a251ae511f"}, ] [package.dependencies] -httpx = "==0.27.*" -pytest = ">=7,<9" +httpx = "==0.28.*" +pytest = "==8.*" [package.extras] -testing = ["pytest-asyncio (==0.23.*)", "pytest-cov (==4.*)"] +testing = ["pytest-asyncio (==0.24.*)", "pytest-cov (==6.*)"] [[package]] name = "pytest-xdist" @@ -1571,7 +1609,7 @@ description = "Python for Window Extensions" optional = false python-versions = "*" groups = ["dev"] -markers = "sys_platform == \"win32\"" +markers = "python_version >= \"3.10\" and sys_platform == \"win32\"" files = [ {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"}, {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"}, @@ -1791,6 +1829,7 @@ description = "Python library for throwaway instances of anything that can run i optional = false python-versions = "<4.0,>=3.9" groups = ["dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "testcontainers-4.8.2-py3-none-any.whl", hash = "sha256:9e19af077cd96e1957c13ee466f1f32905bc6c5bc1bc98643eb18be1a989bfb0"}, {file = "testcontainers-4.8.2.tar.gz", hash = "sha256:dd4a6a2ea09e3c3ecd39e180b6548105929d0bb78d665ce9919cb3f8c98f9853"}, @@ -1980,6 +2019,21 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.0" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, + {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "tzdata" version = "2024.1" @@ -2223,6 +2277,7 @@ description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" groups = ["dev"] +markers = "python_version >= \"3.10\"" files = [ {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, @@ -2339,4 +2394,4 @@ tests = ["Jinja2", "pytest", "pyyaml", "rich"] [metadata] lock-version = "2.1" python-versions = "^3.9, <3.14" -content-hash = "b2747ad942541d2b546562e33cc9cb6d84b26f3d5ca10d72e8f24f55e2a9492e" +content-hash = "5e04cf55024fdf4f1c549eb7b17ef3791d8db85fa8447e92b4584f3d01b7f54c" diff --git a/pyproject.toml b/pyproject.toml index 1e482843..c4b929e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "infrahub-sdk" -version = "1.10.2" +version = "1.11.0" description = "Python Client to interact with Infrahub" authors = ["OpsMill "] readme = "README.md" @@ -64,7 +64,7 @@ pytest-xdist = "^3.3.1" types-python-slugify = "^8.0.0.3" invoke = "^2.2.0" towncrier = "^24.8.0" -infrahub-testcontainers = "^1.1.0b2" +infrahub-testcontainers = { version = "^1.2.5", python = ">=3.10" } astroid = "~3.1" [tool.poetry.extras] diff --git a/tasks.py b/tasks.py index b5e00b17..62ad2299 100644 --- a/tasks.py +++ b/tasks.py @@ -1,6 +1,7 @@ import asyncio import sys from pathlib import Path +from shutil import which from typing import Any from invoke import Context, task @@ -11,6 +12,11 @@ MAIN_DIRECTORY_PATH = Path(__file__).parent +def is_tool_installed(name: str) -> bool: + """Check whether `name` is on PATH and marked as executable.""" + return which(name) is not None + + def _generate(context: Context) -> None: """Generate documentation output from code.""" _generate_infrahubctl_documentation(context=context) @@ -24,14 +30,23 @@ def _generate_infrahubctl_documentation(context: Context) -> None: output_dir = DOCUMENTATION_DIRECTORY / "docs" / "infrahubctl" output_dir.mkdir(parents=True, exist_ok=True) + + # Delete any existing infrahubctl- files in output dir + for file in output_dir.glob("infrahubctl-*"): + file.unlink() + print(" - Generate infrahubctl CLI documentation") for cmd in app.registered_commands: + if cmd.hidden: + continue exec_cmd = f'poetry run typer --func {cmd.name} infrahub_sdk.ctl.cli_commands utils docs --name "infrahubctl {cmd.name}"' exec_cmd += f" --output docs/docs/infrahubctl/infrahubctl-{cmd.name}.mdx" with context.cd(MAIN_DIRECTORY_PATH): context.run(exec_cmd) for cmd in app.registered_groups: + if cmd.hidden: + continue exec_cmd = f"poetry run typer infrahub_sdk.ctl.{cmd.name} utils docs" exec_cmd += f' --name "infrahubctl {cmd.name}" --output docs/docs/infrahubctl/infrahubctl-{cmd.name}.mdx' with context.cd(MAIN_DIRECTORY_PATH): @@ -165,12 +180,46 @@ def lint_ruff(context: Context) -> None: context.run(exec_cmd) +@task +def lint_markdownlint(context: Context) -> None: + """Run markdownlint to check all markdown files.""" + if not is_tool_installed("markdownlint-cli2"): + print(" - markdownlint-cli2 is not installed, skipping documentation linting") + return + + print(" - Check documentation with markdownlint-cli2") + exec_cmd = "markdownlint-cli2 **/*.{md,mdx} --config .markdownlint.yaml" + with context.cd(MAIN_DIRECTORY_PATH): + context.run(exec_cmd) + + +@task +def lint_vale(context: Context) -> None: + """Run vale to check all documentation files.""" + if not is_tool_installed("vale"): + print(" - vale is not installed, skipping documentation style linting") + return + + print(" - Check documentation style with vale") + exec_cmd = r'vale $(find ./docs -type f \( -name "*.mdx" -o -name "*.md" \))' + with context.cd(MAIN_DIRECTORY_PATH): + context.run(exec_cmd) + + +@task +def lint_docs(context: Context) -> None: + """Run all documentation linters.""" + lint_markdownlint(context) + lint_vale(context) + + @task(name="lint") def lint_all(context: Context) -> None: """Run all linters.""" lint_yaml(context) lint_ruff(context) lint_mypy(context) + lint_docs(context) @task(name="docs-validate") diff --git a/tests/conftest.py b/tests/conftest.py index 841eb805..a78523b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import asyncio +import os import pytest @@ -6,6 +7,8 @@ pytest_plugins = ["pytester"] +ENV_VARS_TO_CLEAN = ["INFRAHUB_ADDRESS", "INFRAHUB_TOKEN", "INFRAHUB_BRANCH", "INFRAHUB_USERNAME", "INFRAHUB_PASSWORD"] + @pytest.fixture(scope="session") def event_loop(): @@ -20,3 +23,19 @@ def event_loop(): def execute_before_any_test(): config.SETTINGS.load_and_exit() config.SETTINGS.active.server_address = "http://mock" + + +@pytest.fixture(scope="session", autouse=True) +def clean_env_vars(): + """Cleans the environment variables before any test is run.""" + original_values = {} + for name in ENV_VARS_TO_CLEAN: + original_values[name] = os.environ.get(name) + if original_values[name] is not None: + del os.environ[name] + + yield + + for name in ENV_VARS_TO_CLEAN: + if original_values[name] is not None: + os.environ[name] = original_values[name] diff --git a/tests/fixtures/menus/invalid_menu.yml b/tests/fixtures/menus/invalid_menu.yml new file mode 100644 index 00000000..57aa1d10 --- /dev/null +++ b/tests/fixtures/menus/invalid_menu.yml @@ -0,0 +1,20 @@ +--- +apiVersion: infrahub.app/v1 +kind: Menu +spec: + data: + - nampace: Testing + name: Animal + label: Animals + kind: TestingAnimal + children: + data: + - namespace: Testing + name: Dog + label: Dog + kind: TestingDog + + - namespace: Testing + name: Cat + label: Cat + kind: TestingCat diff --git a/tests/fixtures/menus/invalid_yaml.yml b/tests/fixtures/menus/invalid_yaml.yml new file mode 100644 index 00000000..a7d0cc5e --- /dev/null +++ b/tests/fixtures/menus/invalid_yaml.yml @@ -0,0 +1,20 @@ +--- +apiVersion: infrahub.app/v1 +kind: Menu +spec: + - namespace: Testing + - not valid + name: Animal + label: Animals + kind: TestingAnimal + children: + data: + - namespace: Testing + name: Dog + label: Dog + kind: TestingDog + + - namespace: Testing + name: Cat + label: Cat + kind: TestingCat diff --git a/tests/fixtures/menus/valid_menu.yml b/tests/fixtures/menus/valid_menu.yml new file mode 100644 index 00000000..cba1824c --- /dev/null +++ b/tests/fixtures/menus/valid_menu.yml @@ -0,0 +1,20 @@ +--- +apiVersion: infrahub.app/v1 +kind: Menu +spec: + data: + - namespace: Testing + name: Animal + label: Animals + kind: TestingAnimal + children: + data: + - namespace: Testing + name: Dog + label: Dog + kind: TestingDog + + - namespace: Testing + name: Cat + label: Cat + kind: TestingCat diff --git a/tests/fixtures/schema_05.json b/tests/fixtures/schema_05.json new file mode 100644 index 00000000..193de317 --- /dev/null +++ b/tests/fixtures/schema_05.json @@ -0,0 +1,24557 @@ +{ + "main": "72a3561b868b55a552b7e79762fe379e", + "nodes": [ + { + "id": "1833c77a-3d39-ff5a-3536-c51610a98cb0", + "state": "present", + "name": "MenuItem", + "namespace": "Core", + "description": "Menu Item", + "label": "Menu Item", + "branch": "aware", + "default_filter": null, + "human_friendly_id": [ + "namespace__value", + "name__value" + ], + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": null, + "uniqueness_constraints": [ + [ + "namespace__value", + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Kind", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2500, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "required_permissions", + "kind": "List", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Required Permissions", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 7000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "protected", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Protected", + "description": null, + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 5000, + "default_value": false, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "order_weight", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Order Weight", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 6000, + "default_value": 2000, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "path", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Path", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2500, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "namespace", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[A-Z][a-z0-9]+$", + "max_length": null, + "min_length": null, + "label": "Namespace", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "icon", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Icon", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "section", + "kind": "Text", + "enum": [ + "object", + "internal" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Section", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 8000, + "default_value": "object", + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-3e8c-ae7a-353b-c51ee712cf08", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 15000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-3de3-0124-353c-c51bb1a9492a", + "state": "present", + "name": "children", + "peer": "CoreMenu", + "kind": "Hierarchy", + "label": "Children", + "description": null, + "identifier": "parent__child", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 13000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "inbound", + "hierarchical": "CoreMenu", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-3e3b-6cb8-3539-c51dc096c3d8", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 14000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-3d8a-10c0-3536-c51ea9431165", + "state": "present", + "name": "parent", + "peer": "CoreMenu", + "kind": "Hierarchy", + "label": "Parent", + "description": null, + "identifier": "parent__child", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "outbound", + "hierarchical": "CoreMenu", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreMenu" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": "CoreMenu", + "parent": "CoreMenu", + "children": "CoreMenu", + "kind": "CoreMenuItem", + "hash": "2f1ec73b5643a4bcd34ff82e796d0567" + }, + { + "id": "1833c77a-3ee8-b418-3539-c51742553b89", + "state": "present", + "name": "StandardGroup", + "namespace": "Core", + "description": "Group of nodes of any kind.", + "label": "Standard Group", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:account-group", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "group_type", + "kind": "Text", + "enum": [ + "default", + "internal" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Group Type", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 4000, + "default_value": "default", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-3f3c-c2a6-353f-c51f517132c6", + "state": "present", + "name": "parent", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Parent", + "description": null, + "identifier": "parent__child", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "outbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-4047-608e-353f-c51e30d05e00", + "state": "present", + "name": "children", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Children", + "description": null, + "identifier": "parent__child", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "inbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "members", + "peer": "CoreNode", + "kind": "Generic", + "label": "Members", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "subscribers", + "peer": "CoreNode", + "kind": "Generic", + "label": "Subscribers", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreGroup" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": "CoreGroup", + "parent": "CoreGroup", + "children": "CoreGroup", + "kind": "CoreStandardGroup", + "hash": "8321ed82b37a899808beb33fc82961b8" + }, + { + "id": "1833c77a-40ae-6d94-3537-c514fe861005", + "state": "present", + "name": "GeneratorGroup", + "namespace": "Core", + "description": "Group of nodes that are created by a generator.", + "label": "Generator Group", + "branch": "local", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:state-machine", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "local", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "group_type", + "kind": "Text", + "enum": [ + "default", + "internal" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Group Type", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 4000, + "default_value": "default", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-4124-c9b5-353c-c51174b74c0c", + "state": "present", + "name": "parent", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Parent", + "description": null, + "identifier": "parent__child", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "outbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-4181-832d-353b-c51a574b8529", + "state": "present", + "name": "children", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Children", + "description": null, + "identifier": "parent__child", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "inbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "members", + "peer": "CoreNode", + "kind": "Generic", + "label": "Members", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "local", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "subscribers", + "peer": "CoreNode", + "kind": "Generic", + "label": "Subscribers", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "local", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreGroup" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": "CoreGroup", + "parent": "CoreGroup", + "children": "CoreGroup", + "kind": "CoreGeneratorGroup", + "hash": "1e97c61f155d88eb41c5e2b3d1e60334" + }, + { + "id": "1833c77a-41d6-76bc-3535-c5180425d75f", + "state": "present", + "name": "GraphQLQueryGroup", + "namespace": "Core", + "description": "Group of nodes associated with a given GraphQLQuery.", + "label": "GraphQL Query Group", + "branch": "local", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:account-group", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-4227-99bb-3533-c51156d8881b", + "state": "present", + "name": "parameters", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Parameters", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "local", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "group_type", + "kind": "Text", + "enum": [ + "default", + "internal" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Group Type", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 4000, + "default_value": "default", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-4352-c6c3-3537-c510bc5549c0", + "state": "present", + "name": "children", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Children", + "description": null, + "identifier": "parent__child", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "inbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-427c-4918-3530-c51e4fa5f836", + "state": "present", + "name": "query", + "peer": "CoreGraphQLQuery", + "kind": "Attribute", + "label": "Query", + "description": null, + "identifier": "coregraphqlquery__coregraphqlquerygroup", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "local", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-42d3-5c1c-3533-c51f40e206c0", + "state": "present", + "name": "parent", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Parent", + "description": null, + "identifier": "parent__child", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "outbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "members", + "peer": "CoreNode", + "kind": "Generic", + "label": "Members", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "local", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "subscribers", + "peer": "CoreNode", + "kind": "Generic", + "label": "Subscribers", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "local", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreGroup" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": "CoreGroup", + "parent": "CoreGroup", + "children": "CoreGroup", + "kind": "CoreGraphQLQueryGroup", + "hash": "14ad013a3a1ec9b5fef577123b6cc8de" + }, + { + "id": "1833c77a-43b7-c594-3530-c5130cc33c69", + "state": "present", + "name": "Tag", + "namespace": "Builtin", + "description": "Standard Tag object to attached to other objects to provide some context.", + "label": "Tag", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": true, + "menu_placement": null, + "icon": "mdi:tag-multiple", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-4419-5adc-353a-c51546bbf282", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-447c-3992-3539-c5181626d9a4", + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-44ef-71fd-3531-c51de98672c0", + "state": "present", + "name": "profiles", + "peer": "CoreProfile", + "kind": "Profile", + "label": "Profiles", + "description": null, + "identifier": "node__profile", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 3000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-45a8-4eb2-353a-c51e27d68bee", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-454b-8283-353e-c519a9da6ad2", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 4000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [], + "generate_profile": true, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "BuiltinTag", + "hash": "092aa4b4237ed1c41786a0ad8d55825a" + }, + { + "id": "1833c77a-4627-9cb4-3539-c51c9c496bca", + "state": "present", + "name": "Account", + "namespace": "Core", + "description": "User Account for Infrahub", + "label": "Account", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:account", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "account_type", + "kind": "Text", + "enum": [ + "User", + "Script", + "Bot", + "Git" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Account Type", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": "User", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "inactive", + "description": "Account is not allowed to login", + "color": "#e74c3c", + "label": "Inactive" + }, + { + "id": null, + "state": "present", + "name": "active", + "description": "Account is allowed to login", + "color": "#52be80", + "label": "Active" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "active", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "password", + "kind": "HashedPassword", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Password", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-46da-b5d3-3531-c51c84b19014", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-4686-224b-3536-c514275e41be", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "LineageOwner", + "LineageSource", + "CoreGenericAccount" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreAccount", + "hash": "5d575b012bdd7f411339323659e9b061" + }, + { + "id": "1833c77a-4a8f-9e8f-353c-c51401e47982", + "state": "present", + "name": "PasswordCredential", + "namespace": "Core", + "description": "Username/Password based credential", + "label": "Username / Password", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:key-variant", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-4b51-eb1f-353b-c5148f2bcbc9", + "state": "present", + "name": "password", + "kind": "Password", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Password", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 7000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-4afc-cc44-353e-c513e7c914e5", + "state": "present", + "name": "username", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Username", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-4bad-3db3-353d-c515ff23ad61", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-4c0e-df31-353f-c51e109202bd", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreCredential" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CorePasswordCredential", + "hash": "eb5dbbe88bc5a8b092001e4b2a149fd4" + }, + { + "id": "1833c77a-4e38-d7c2-3532-c51139f99a47", + "state": "present", + "name": "ProposedChange", + "namespace": "Core", + "description": "Metadata related to a proposed change", + "label": "Proposed Change", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": null, + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:file-replace-outline", + "order_by": null, + "uniqueness_constraints": null, + "documentation": "/topics/proposed-change", + "attributes": [ + { + "id": "1833c77a-4e89-d5f9-353f-c51cb0406c57", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-4edd-a396-353b-c51878cff432", + "state": "present", + "name": "description", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-4fdd-73de-3534-c516fdfec21d", + "state": "present", + "name": "state", + "kind": "Text", + "enum": [ + "open", + "merged", + "merging", + "closed", + "canceled" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "State", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": "open", + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-4f30-71fd-3538-c51f3afdafa5", + "state": "present", + "name": "source_branch", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Source Branch", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-4f85-e8bc-3530-c51063b2aa4b", + "state": "present", + "name": "destination_branch", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Destination Branch", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-52c8-beab-3536-c51885a35c8f", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 13000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-51b7-f198-3539-c51f223c32e3", + "state": "present", + "name": "threads", + "peer": "CoreThread", + "kind": "Component", + "label": "Threads", + "description": null, + "identifier": "proposedchange__thread", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-514e-cacf-353f-c51a74c6590a", + "state": "present", + "name": "comments", + "peer": "CoreChangeComment", + "kind": "Component", + "label": "Comments", + "description": null, + "identifier": "corechangecomment__coreproposedchange", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-50f2-d2b4-3533-c51e63c2c6d7", + "state": "present", + "name": "created_by", + "peer": "CoreGenericAccount", + "kind": "Attribute", + "label": "Created By", + "description": null, + "identifier": "coreaccount__proposedchange_created_by", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 8000, + "optional": true, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5038-b367-353a-c51f1aaf5fdc", + "state": "present", + "name": "approved_by", + "peer": "CoreGenericAccount", + "kind": "Attribute", + "label": "Approved By", + "description": null, + "identifier": "coreaccount__proposedchange_approved_by", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5274-8043-3530-c51ab523f1ed", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5095-135a-353e-c514f52375ac", + "state": "present", + "name": "reviewers", + "peer": "CoreGenericAccount", + "kind": "Attribute", + "label": "Reviewers", + "description": null, + "identifier": "coreaccount__proposedchange_reviewed_by", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-521c-663a-353c-c518fcef9452", + "state": "present", + "name": "validations", + "peer": "CoreValidator", + "kind": "Component", + "label": "Validations", + "description": null, + "identifier": "proposed_change__validator", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreProposedChange", + "hash": "64b842a159c781033b3b61b4e7095240" + }, + { + "id": "1833c77a-5329-0a33-3538-c5162094728f", + "state": "present", + "name": "ChangeThread", + "namespace": "Core", + "description": "A thread on proposed change", + "label": "Change Thread", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": null, + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "created_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "resolved", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Resolved", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": false, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-53d5-801a-3537-c511b5f29629", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-537c-653b-3538-c5102b754683", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Change", + "description": null, + "identifier": "proposedchange__thread", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 4000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_by", + "peer": "CoreGenericAccount", + "kind": "Generic", + "label": "Created By", + "description": null, + "identifier": "thread__account", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 6000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "comments", + "peer": "CoreThreadComment", + "kind": "Component", + "label": "Comments", + "description": null, + "identifier": "thread__threadcomment", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreThread" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreChangeThread", + "hash": "fdd36f63f63afdabc05281dbdd3bb109" + }, + { + "id": "1833c77a-5450-1908-353a-c5169ba02060", + "state": "present", + "name": "FileThread", + "namespace": "Core", + "description": "A thread related to a file on a proposed change", + "label": "Thread - File", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": null, + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "created_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-5511-cee0-353a-c518d37d97ae", + "state": "present", + "name": "commit", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Commit", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-54ac-b77b-3533-c514e7dac89e", + "state": "present", + "name": "file", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "File", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-5562-d29f-3539-c51ecd11eab2", + "state": "present", + "name": "line_number", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Line Number", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "resolved", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Resolved", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": false, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-55b3-d082-353b-c51232cb20b7", + "state": "present", + "name": "repository", + "peer": "CoreRepository", + "kind": "Generic", + "label": "Repository", + "description": null, + "identifier": "corefilethread__corerepository", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 7000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5662-8aa6-3537-c51cb67c2b01", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-560a-0da8-3531-c5114998bd8b", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Change", + "description": null, + "identifier": "proposedchange__thread", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 4000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_by", + "peer": "CoreGenericAccount", + "kind": "Generic", + "label": "Created By", + "description": null, + "identifier": "thread__account", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 6000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "comments", + "peer": "CoreThreadComment", + "kind": "Component", + "label": "Comments", + "description": null, + "identifier": "thread__threadcomment", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreThread" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreFileThread", + "hash": "b548aa41621c07539408757af6bf93fd" + }, + { + "id": "1833c77a-56c3-9f07-3539-c511b230695f", + "state": "present", + "name": "ArtifactThread", + "namespace": "Core", + "description": "A thread related to an artifact on a proposed change", + "label": "Thread - Artifact", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": null, + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "created_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-5719-311d-3531-c51443adbf4d", + "state": "present", + "name": "artifact_id", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Artifact Id", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-57c6-e6ad-3530-c519e47feacd", + "state": "present", + "name": "line_number", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Line Number", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-5770-0c4d-3534-c51d377993e0", + "state": "present", + "name": "storage_id", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Storage Id", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "resolved", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Resolved", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": false, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-587e-b21c-3537-c5185fe3e60c", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5822-feb0-353a-c51e11ec8d63", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Change", + "description": null, + "identifier": "proposedchange__thread", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 4000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_by", + "peer": "CoreGenericAccount", + "kind": "Generic", + "label": "Created By", + "description": null, + "identifier": "thread__account", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 6000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "comments", + "peer": "CoreThreadComment", + "kind": "Component", + "label": "Comments", + "description": null, + "identifier": "thread__threadcomment", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreThread" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreArtifactThread", + "hash": "25568e7d372b58c61ad72604a13661ea" + }, + { + "id": "1833c77a-58dd-7e57-3536-c51d63a777c9", + "state": "present", + "name": "ObjectThread", + "namespace": "Core", + "description": "A thread related to an object on a proposed change", + "label": "Thread - Object", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": null, + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "created_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-593e-d7e4-3536-c5124f35826f", + "state": "present", + "name": "object_path", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Object Path", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "resolved", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Resolved", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": false, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-59fd-4b96-353d-c516cee68b64", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-59a8-1040-3532-c510cac66bb1", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Change", + "description": null, + "identifier": "proposedchange__thread", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 4000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_by", + "peer": "CoreGenericAccount", + "kind": "Generic", + "label": "Created By", + "description": null, + "identifier": "thread__account", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 6000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "comments", + "peer": "CoreThreadComment", + "kind": "Component", + "label": "Comments", + "description": null, + "identifier": "thread__threadcomment", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreThread" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreObjectThread", + "hash": "17251ce1f78adae56ad5a0ddcbe02606" + }, + { + "id": "1833c77a-5a5b-0925-3534-c516db295bde", + "state": "present", + "name": "ChangeComment", + "namespace": "Core", + "description": "A comment on proposed change", + "label": "Change Comment", + "branch": "agnostic", + "default_filter": "text__value", + "human_friendly_id": null, + "display_labels": [ + "text__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "created_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "text", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Text", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-5b64-1bde-3535-c51c9d95a36a", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5aad-4346-3531-c5172defe282", + "state": "present", + "name": "change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Change", + "description": null, + "identifier": "corechangecomment__coreproposedchange", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 3000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5b0e-cbdd-353d-c515ac9db4f5", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_by", + "peer": "CoreGenericAccount", + "kind": "Generic", + "label": "Created By", + "description": null, + "identifier": "comment__account", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 3000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreComment" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreChangeComment", + "hash": "52897a1720a5413cd81847abd563ea00" + }, + { + "id": "1833c77a-5bc0-5f02-353d-c511defa073b", + "state": "present", + "name": "ThreadComment", + "namespace": "Core", + "description": "A comment on thread within a Proposed Change", + "label": "Thread Comment", + "branch": "agnostic", + "default_filter": "text__value", + "human_friendly_id": null, + "display_labels": [ + "text__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "created_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "text", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Text", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-5cc2-e2dc-353b-c517c870aa94", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5c6f-013a-3532-c51d6e688932", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5c1d-b413-3538-c51438879885", + "state": "present", + "name": "thread", + "peer": "CoreThread", + "kind": "Parent", + "label": "Thread", + "description": null, + "identifier": "thread__threadcomment", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 3000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_by", + "peer": "CoreGenericAccount", + "kind": "Generic", + "label": "Created By", + "description": null, + "identifier": "comment__account", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 3000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreComment" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreThreadComment", + "hash": "b250c3a7a4803881d560af1d6718b4f9" + }, + { + "id": "1833c77a-5d1c-411d-3534-c51679ecedb1", + "state": "present", + "name": "Repository", + "namespace": "Core", + "description": "A Git Repository integrated with Infrahub", + "label": "Repository", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:source-repository", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ], + [ + "location__value" + ] + ], + "documentation": "/topics/repository", + "attributes": [ + { + "id": "1833c77a-5dcc-11be-3538-c51dd2c17c20", + "state": "present", + "name": "commit", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Commit", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 7000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-5d76-322e-3536-c51e70c58f79", + "state": "present", + "name": "default_branch", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Default Branch", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "main", + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "operational_status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "unknown", + "description": "Status of the repository is unknown and mostlikely because it hasn't been synced yet", + "color": "#9ca3af", + "label": "Unknown" + }, + { + "id": null, + "state": "present", + "name": "online", + "description": "Repository connection is working", + "color": "#86efac", + "label": "Online" + }, + { + "id": null, + "state": "present", + "name": "error-cred", + "description": "Repository can't be synced due to some credential error(s)", + "color": "#f87171", + "label": "Credential Error" + }, + { + "id": null, + "state": "present", + "name": "error-connection", + "description": "Repository can't be synced due to some connectivity error(s)", + "color": "#f87171", + "label": "Connectivity Error" + }, + { + "id": null, + "state": "present", + "name": "error", + "description": "Repository can't be synced due to an unknown error", + "color": "#ef4444", + "label": "Error" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Operational Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "internal_status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "staging", + "description": "Repository was recently added to this branch.", + "color": "#fef08a", + "label": "Staging" + }, + { + "id": null, + "state": "present", + "name": "inactive", + "description": "Repository is not active on this branch.", + "color": "#e5e7eb", + "label": "Inactive" + }, + { + "id": null, + "state": "present", + "name": "active", + "description": "Repository is actively being synced for this branch", + "color": "#86efac", + "label": "Active" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Internal Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 7000, + "default_value": "inactive", + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[^/]*$", + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "location", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Location", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "sync_status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "unknown", + "description": "Status of the repository is unknown and mostlikely because it hasn't been synced yet", + "color": "#9ca3af", + "label": "Unknown" + }, + { + "id": null, + "state": "present", + "name": "syncing", + "description": "A sync job is currently running against the repository.", + "color": "#a855f7", + "label": "Syncing" + }, + { + "id": null, + "state": "present", + "name": "in-sync", + "description": "The repository is syncing correctly", + "color": "#60a5fa", + "label": "In Sync" + }, + { + "id": null, + "state": "present", + "name": "error-import", + "description": "Repository import error observed", + "color": "#f87171", + "label": "Import Error" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Sync Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-5e26-2332-353f-c51552b2dfd4", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 15000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5e80-a283-3531-c5106a97e93d", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 16000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "generators", + "peer": "CoreGeneratorDefinition", + "kind": "Generic", + "label": "Generators", + "description": null, + "identifier": "generator_definition__repository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheckDefinition", + "kind": "Generic", + "label": "Checks", + "description": null, + "identifier": "check_definition__repository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "credential", + "peer": "CoreCredential", + "kind": "Attribute", + "label": "Credential", + "description": null, + "identifier": "gitrepository__credential", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 4000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "tags", + "peer": "BuiltinTag", + "kind": "Attribute", + "label": "Tags", + "description": null, + "identifier": "builtintag__coregenericrepository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "transformations", + "peer": "CoreTransformation", + "kind": "Generic", + "label": "Transformations", + "description": null, + "identifier": "repository__transformation", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "queries", + "peer": "CoreGraphQLQuery", + "kind": "Generic", + "label": "Queries", + "description": null, + "identifier": "graphql_query__repository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "LineageOwner", + "LineageSource", + "CoreGenericRepository", + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreRepository", + "hash": "c8bef5f7683b61e0a16f14a60d660eaf" + }, + { + "id": "1833c77a-5ee8-2001-353e-c511ea413495", + "state": "present", + "name": "ReadOnlyRepository", + "namespace": "Core", + "description": "A Git Repository integrated with Infrahub, Git-side will not be updated", + "label": "Read-Only Repository", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:source-repository", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ], + [ + "location__value" + ] + ], + "documentation": "/topics/repository", + "attributes": [ + { + "id": "1833c77a-5f3d-046d-3537-c51020dd49f7", + "state": "present", + "name": "ref", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Ref", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 6000, + "default_value": "main", + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-5f96-55fc-3537-c5141b7a7e6a", + "state": "present", + "name": "commit", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Commit", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 7000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "operational_status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "unknown", + "description": "Status of the repository is unknown and mostlikely because it hasn't been synced yet", + "color": "#9ca3af", + "label": "Unknown" + }, + { + "id": null, + "state": "present", + "name": "online", + "description": "Repository connection is working", + "color": "#86efac", + "label": "Online" + }, + { + "id": null, + "state": "present", + "name": "error-cred", + "description": "Repository can't be synced due to some credential error(s)", + "color": "#f87171", + "label": "Credential Error" + }, + { + "id": null, + "state": "present", + "name": "error-connection", + "description": "Repository can't be synced due to some connectivity error(s)", + "color": "#f87171", + "label": "Connectivity Error" + }, + { + "id": null, + "state": "present", + "name": "error", + "description": "Repository can't be synced due to an unknown error", + "color": "#ef4444", + "label": "Error" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Operational Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "internal_status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "staging", + "description": "Repository was recently added to this branch.", + "color": "#fef08a", + "label": "Staging" + }, + { + "id": null, + "state": "present", + "name": "inactive", + "description": "Repository is not active on this branch.", + "color": "#e5e7eb", + "label": "Inactive" + }, + { + "id": null, + "state": "present", + "name": "active", + "description": "Repository is actively being synced for this branch", + "color": "#86efac", + "label": "Active" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Internal Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 7000, + "default_value": "inactive", + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[^/]*$", + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "location", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Location", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "sync_status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "unknown", + "description": "Status of the repository is unknown and mostlikely because it hasn't been synced yet", + "color": "#9ca3af", + "label": "Unknown" + }, + { + "id": null, + "state": "present", + "name": "syncing", + "description": "A sync job is currently running against the repository.", + "color": "#a855f7", + "label": "Syncing" + }, + { + "id": null, + "state": "present", + "name": "in-sync", + "description": "The repository is syncing correctly", + "color": "#60a5fa", + "label": "In Sync" + }, + { + "id": null, + "state": "present", + "name": "error-import", + "description": "Repository import error observed", + "color": "#f87171", + "label": "Import Error" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Sync Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-6044-ab0e-3536-c51ead510b6e", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 16000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-5fee-15da-353d-c51334ac4291", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 15000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "generators", + "peer": "CoreGeneratorDefinition", + "kind": "Generic", + "label": "Generators", + "description": null, + "identifier": "generator_definition__repository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheckDefinition", + "kind": "Generic", + "label": "Checks", + "description": null, + "identifier": "check_definition__repository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "credential", + "peer": "CoreCredential", + "kind": "Attribute", + "label": "Credential", + "description": null, + "identifier": "gitrepository__credential", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 4000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "tags", + "peer": "BuiltinTag", + "kind": "Attribute", + "label": "Tags", + "description": null, + "identifier": "builtintag__coregenericrepository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "transformations", + "peer": "CoreTransformation", + "kind": "Generic", + "label": "Transformations", + "description": null, + "identifier": "repository__transformation", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "queries", + "peer": "CoreGraphQLQuery", + "kind": "Generic", + "label": "Queries", + "description": null, + "identifier": "graphql_query__repository", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "LineageOwner", + "LineageSource", + "CoreGenericRepository", + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreReadOnlyRepository", + "hash": "d87b41f0c784419af06e716923d6eeaf" + }, + { + "id": "1833c77a-60ae-9b02-3537-c51b8a5f9f47", + "state": "present", + "name": "TransformJinja2", + "namespace": "Core", + "description": "A file rendered from a Jinja2 template", + "label": "Transform Jinja2", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:cog-transfer", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": "/topics/transformation", + "attributes": [ + { + "id": "1833c77a-6106-41cf-3538-c51f5a1e3308", + "state": "present", + "name": "template_path", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Template Path", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "timeout", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Timeout", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 4000, + "default_value": 10, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-6166-ddbe-3534-c514f1b30f2f", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-61d1-8e92-353e-c5139ea9ec02", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "repository", + "peer": "CoreGenericRepository", + "kind": "Attribute", + "label": "Repository", + "description": null, + "identifier": "repository__transformation", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "query", + "peer": "CoreGraphQLQuery", + "kind": "Attribute", + "label": "Query", + "description": null, + "identifier": "graphql_query__transformation", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 5000, + "optional": false, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "tags", + "peer": "BuiltinTag", + "kind": "Attribute", + "label": "Tags", + "description": null, + "identifier": "builtintag__coretransformation", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTransformation" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreTransformJinja2", + "hash": "2cb207fbfb135dfc5519a29634015440" + }, + { + "id": "1833c77a-6234-085e-353a-c5108efd94ca", + "state": "present", + "name": "DataCheck", + "namespace": "Core", + "description": "A check related to some Data", + "label": "Data Check", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": null, + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-6330-713b-3531-c51b9e721dd8", + "state": "present", + "name": "enriched_conflict_id", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Enriched Conflict Id", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-6288-9a6f-353a-c51d58e112cf", + "state": "present", + "name": "conflicts", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conflicts", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-62db-6eb2-3536-c51e10d6f379", + "state": "present", + "name": "keep_branch", + "kind": "Text", + "enum": [ + "target", + "source" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Keep Branch", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "message", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Message", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[A-Z][a-zA-Z0-9]+$", + "max_length": 32, + "min_length": 3, + "label": "Kind", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "origin", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Origin", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 8000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "severity", + "kind": "Text", + "enum": [ + "success", + "info", + "warning", + "error", + "critical" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Severity", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 7000, + "default_value": "info", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-638c-1725-353c-c515e11f1d5a", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 13000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-63e7-d455-353c-c5129b1d269c", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 14000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validator", + "peer": "CoreValidator", + "kind": "Parent", + "label": "Validator", + "description": null, + "identifier": "validator__check", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 9000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreCheck" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreDataCheck", + "hash": "b241fc1dca267b2697de22245c79307b" + }, + { + "id": "1833c77a-6450-dae3-3539-c5169d474a5c", + "state": "present", + "name": "StandardCheck", + "namespace": "Core", + "description": "A standard check", + "label": "Standard Check", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": null, + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "message", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Message", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[A-Z][a-zA-Z0-9]+$", + "max_length": 32, + "min_length": 3, + "label": "Kind", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "origin", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Origin", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 8000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "severity", + "kind": "Text", + "enum": [ + "success", + "info", + "warning", + "error", + "critical" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Severity", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 7000, + "default_value": "info", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-64b1-1f6b-3530-c5111594b6a7", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-6515-904f-3535-c51833712c93", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validator", + "peer": "CoreValidator", + "kind": "Parent", + "label": "Validator", + "description": null, + "identifier": "validator__check", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 9000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreCheck" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreStandardCheck", + "hash": "3070cb556ae15bd0e4722128fbe418d7" + }, + { + "id": "1833c77a-658d-55ee-3531-c51d0de6c7b0", + "state": "present", + "name": "SchemaCheck", + "namespace": "Core", + "description": "A check related to the schema", + "label": "Schema Check", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": null, + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-6646-e600-353a-c51754c4f758", + "state": "present", + "name": "enriched_conflict_id", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Enriched Conflict Id", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-65e7-ee27-353c-c516ffac5e86", + "state": "present", + "name": "conflicts", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conflicts", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "message", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Message", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[A-Z][a-zA-Z0-9]+$", + "max_length": 32, + "min_length": 3, + "label": "Kind", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "origin", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Origin", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 8000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "severity", + "kind": "Text", + "enum": [ + "success", + "info", + "warning", + "error", + "critical" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Severity", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 7000, + "default_value": "info", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-66ef-84ab-353b-c51c6a5c1eab", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 13000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-669c-442b-3533-c5107d162a08", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validator", + "peer": "CoreValidator", + "kind": "Parent", + "label": "Validator", + "description": null, + "identifier": "validator__check", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 9000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreCheck" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreSchemaCheck", + "hash": "b1586c11a0381025f1158f686f9ebdcf" + }, + { + "id": "1833c77a-675f-f30e-3530-c516c94a8862", + "state": "present", + "name": "FileCheck", + "namespace": "Core", + "description": "A check related to a file in a Git Repository", + "label": "File Check", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": null, + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-680d-e772-353d-c513b1c449f7", + "state": "present", + "name": "commit", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Commit", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-67bb-e20d-353c-c5145654046a", + "state": "present", + "name": "files", + "kind": "List", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Files", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "message", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Message", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[A-Z][a-zA-Z0-9]+$", + "max_length": 32, + "min_length": 3, + "label": "Kind", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "origin", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Origin", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 8000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "severity", + "kind": "Text", + "enum": [ + "success", + "info", + "warning", + "error", + "critical" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Severity", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 7000, + "default_value": "info", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-68c1-2724-353d-c517d74e3191", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 13000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-686b-9b90-3538-c51dde78b08b", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validator", + "peer": "CoreValidator", + "kind": "Parent", + "label": "Validator", + "description": null, + "identifier": "validator__check", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 9000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreCheck" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreFileCheck", + "hash": "3dc6a5045041680d9e0120468bbe90b8" + }, + { + "id": "1833c77a-6922-53ee-3535-c51c0a5ed41c", + "state": "present", + "name": "ArtifactCheck", + "namespace": "Core", + "description": "A check related to an artifact", + "label": "Artifact Check", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": null, + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-6a88-d0b8-3530-c51136d3edb2", + "state": "present", + "name": "storage_id", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Storage Id", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-6979-82c7-353a-c5192f6cb9ac", + "state": "present", + "name": "changed", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Changed", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-69ce-17c6-3536-c5196d7ec5ee", + "state": "present", + "name": "checksum", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Checksum", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-6a2b-ba27-3530-c51e93c0cfd1", + "state": "present", + "name": "artifact_id", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Artifact Id", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-6ae5-49e5-3535-c515a5076534", + "state": "present", + "name": "line_number", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Line Number", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "message", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Message", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[A-Z][a-zA-Z0-9]+$", + "max_length": 32, + "min_length": 3, + "label": "Kind", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "origin", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Origin", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 8000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "severity", + "kind": "Text", + "enum": [ + "success", + "info", + "warning", + "error", + "critical" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Severity", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 7000, + "default_value": "info", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-6b95-5de7-3531-c51311692025", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 16000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-6b3a-ab7b-353b-c51901e7848a", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 15000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validator", + "peer": "CoreValidator", + "kind": "Parent", + "label": "Validator", + "description": null, + "identifier": "validator__check", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 9000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreCheck" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreArtifactCheck", + "hash": "30d3b8ac2f26ef35cd1f0fca2e7ebff4" + }, + { + "id": "1833c77a-6bf8-4e07-3535-c5138d578930", + "state": "present", + "name": "GeneratorCheck", + "namespace": "Core", + "description": "A check related to a Generator instance", + "label": "Generator Check", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": null, + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": "1833c77a-6c4d-c737-353f-c519e6ec27c0", + "state": "present", + "name": "instance", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Instance", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "message", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Message", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 6000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "^[A-Z][a-zA-Z0-9]+$", + "max_length": 32, + "min_length": 3, + "label": "Kind", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "origin", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Origin", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "created_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Created At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 8000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "severity", + "kind": "Text", + "enum": [ + "success", + "info", + "warning", + "error", + "critical" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Severity", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 7000, + "default_value": "info", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-6ca4-97a9-3535-c51e3bb0e8b7", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-6d01-d88b-353c-c51cdcec1be3", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validator", + "peer": "CoreValidator", + "kind": "Parent", + "label": "Validator", + "description": null, + "identifier": "validator__check", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 9000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreCheck" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreGeneratorCheck", + "hash": "954d07e69fa7f2edf51295af5590dcb4" + }, + { + "id": "1833c77a-6d6b-2804-3536-c51395fad49c", + "state": "present", + "name": "DataValidator", + "namespace": "Core", + "description": "A check to validate the data integrity between two branches", + "label": "Data Validator", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "started_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "completed_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Completed At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "state", + "kind": "Text", + "enum": [ + "queued", + "in_progress", + "completed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "State", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "queued", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "started_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Started At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-6e57-60d6-353f-c5173a6203b2", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-6dd0-c0ce-3537-c51d587c5f02", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "proposed_change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Proposed Change", + "description": null, + "identifier": "proposed_change__validator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheck", + "kind": "Component", + "label": "Checks", + "description": null, + "identifier": "validator__check", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreValidator" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreDataValidator", + "hash": "067f31fad6e1d8d0dbfede2f8e7427fb" + }, + { + "id": "1833c77a-6ebc-e836-3530-c51799d8a6bb", + "state": "present", + "name": "RepositoryValidator", + "namespace": "Core", + "description": "A Validator related to a specific repository", + "label": "Repository Validator", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "started_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "completed_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Completed At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "state", + "kind": "Text", + "enum": [ + "queued", + "in_progress", + "completed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "State", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "queued", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "started_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Started At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-6fcd-59fa-3535-c519f1227150", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-6f6f-a7d0-353d-c51936aecc2b", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-6f18-de0a-353d-c51b86d594c3", + "state": "present", + "name": "repository", + "peer": "CoreGenericRepository", + "kind": "Attribute", + "label": "Repository", + "description": null, + "identifier": "coregenericrepository__corerepositoryvalidator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "proposed_change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Proposed Change", + "description": null, + "identifier": "proposed_change__validator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheck", + "kind": "Component", + "label": "Checks", + "description": null, + "identifier": "validator__check", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreValidator" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreRepositoryValidator", + "hash": "9e78b32ed642ef92544760d9eb09f0ed" + }, + { + "id": "1833c77a-7030-55fc-353e-c51aada07976", + "state": "present", + "name": "UserValidator", + "namespace": "Core", + "description": "A Validator related to a user defined checks in a repository", + "label": "User Validator", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "started_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "completed_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Completed At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "state", + "kind": "Text", + "enum": [ + "queued", + "in_progress", + "completed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "State", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "queued", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "started_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Started At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-7143-0070-3532-c512eb7e1b15", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-71b2-ca70-353b-c515f77fc553", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-70ec-06a6-3532-c512d9a91316", + "state": "present", + "name": "repository", + "peer": "CoreGenericRepository", + "kind": "Attribute", + "label": "Repository", + "description": null, + "identifier": "coregenericrepository__coreuservalidator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 7000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-708c-fdb6-353d-c51e9371c92c", + "state": "present", + "name": "check_definition", + "peer": "CoreCheckDefinition", + "kind": "Attribute", + "label": "Check Definition", + "description": null, + "identifier": "corecheckdefinition__coreuservalidator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "proposed_change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Proposed Change", + "description": null, + "identifier": "proposed_change__validator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheck", + "kind": "Component", + "label": "Checks", + "description": null, + "identifier": "validator__check", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreValidator" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreUserValidator", + "hash": "65baa1d572f0f928fcf166d00e192f69" + }, + { + "id": "1833c77a-7224-5fea-353e-c5119ddd2a61", + "state": "present", + "name": "SchemaValidator", + "namespace": "Core", + "description": "A validator related to the schema", + "label": "Schema Validator", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "started_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "completed_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Completed At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "state", + "kind": "Text", + "enum": [ + "queued", + "in_progress", + "completed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "State", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "queued", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "started_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Started At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-72e6-882d-3532-c51397d23261", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-7284-bc88-353f-c51426a9bc0e", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "proposed_change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Proposed Change", + "description": null, + "identifier": "proposed_change__validator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheck", + "kind": "Component", + "label": "Checks", + "description": null, + "identifier": "validator__check", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreValidator" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreSchemaValidator", + "hash": "85e18bb6ceafa367b1760d597cf4daf1" + }, + { + "id": "1833c77a-734c-2821-3530-c51949c1d9cf", + "state": "present", + "name": "ArtifactValidator", + "namespace": "Core", + "description": "A validator related to the artifacts", + "label": "Artifact Validator", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "started_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "completed_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Completed At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "state", + "kind": "Text", + "enum": [ + "queued", + "in_progress", + "completed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "State", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "queued", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "started_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Started At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-7408-cde9-3534-c514c1ec3f1b", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-73a6-6175-3531-c510e2ffeeed", + "state": "present", + "name": "definition", + "peer": "CoreArtifactDefinition", + "kind": "Attribute", + "label": "Definition", + "description": null, + "identifier": "coreartifactdefinition__coreartifactvalidator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-746f-ec2f-353c-c51589dee50f", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "proposed_change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Proposed Change", + "description": null, + "identifier": "proposed_change__validator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheck", + "kind": "Component", + "label": "Checks", + "description": null, + "identifier": "validator__check", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreValidator" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreArtifactValidator", + "hash": "e8e6cbe338021887a9608664c316293e" + }, + { + "id": "1833c77a-74d8-3dc0-353f-c51ff37ab04d", + "state": "present", + "name": "GeneratorValidator", + "namespace": "Core", + "description": "A validator related to generators", + "label": "Generator Validator", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": null, + "display_labels": [ + "label__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": null, + "order_by": [ + "started_at__value" + ], + "uniqueness_constraints": null, + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "completed_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Completed At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "conclusion", + "kind": "Text", + "enum": [ + "unknown", + "failure", + "success" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Conclusion", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": "unknown", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "state", + "kind": "Text", + "enum": [ + "queued", + "in_progress", + "completed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "State", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "queued", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "started_at", + "kind": "DateTime", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Started At", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-752f-bcd7-3534-c51d7d13024b", + "state": "present", + "name": "definition", + "peer": "CoreGeneratorDefinition", + "kind": "Attribute", + "label": "Definition", + "description": null, + "identifier": "coregeneratordefinition__coregeneratorvalidator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-75e9-54b9-3532-c51e6d172b34", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-7590-cc55-353e-c5168dac4128", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "proposed_change", + "peer": "CoreProposedChange", + "kind": "Parent", + "label": "Proposed Change", + "description": null, + "identifier": "proposed_change__validator", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "checks", + "peer": "CoreCheck", + "kind": "Component", + "label": "Checks", + "description": null, + "identifier": "validator__check", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "agnostic", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreValidator" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreGeneratorValidator", + "hash": "dee5f753c9c5cce877c46f4062c28f44" + }, + { + "id": "1833c77a-7646-1ef4-3534-c5115fb12abd", + "state": "present", + "name": "CheckDefinition", + "namespace": "Core", + "description": null, + "label": "Check Definition", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:check-all", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-769c-32c3-3539-c51076036b95", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-76ef-6bc6-353c-c51b87478bf6", + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-77b7-f968-353b-c514ebd5bfaa", + "state": "present", + "name": "class_name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Class Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-774e-4fc4-3533-c5122f5cf38e", + "state": "present", + "name": "file_path", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "File Path", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7818-fecf-3539-c518dc8f9755", + "state": "present", + "name": "timeout", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Timeout", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 5000, + "default_value": 10, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7876-9401-3537-c5195eedbe81", + "state": "present", + "name": "parameters", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Parameters", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 6000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-78d6-d59b-353d-c5181466060e", + "state": "present", + "name": "repository", + "peer": "CoreGenericRepository", + "kind": "Attribute", + "label": "Repository", + "description": null, + "identifier": "check_definition__repository", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 7000, + "optional": false, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-792f-84e5-3531-c519d948d14b", + "state": "present", + "name": "query", + "peer": "CoreGraphQLQuery", + "kind": "Attribute", + "label": "Query", + "description": null, + "identifier": "check_definition__graphql_query", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-7abe-ebf7-353b-c51989dbd389", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-798b-a587-3534-c518ecc1321e", + "state": "present", + "name": "targets", + "peer": "CoreGroup", + "kind": "Attribute", + "label": "Targets", + "description": null, + "identifier": "check_definition___group", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-79e7-3813-3536-c51d33ee09b0", + "state": "present", + "name": "tags", + "peer": "BuiltinTag", + "kind": "Attribute", + "label": "Tags", + "description": null, + "identifier": "builtintag__corecheckdefinition", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-7a5e-e9d7-353c-c51209f8f734", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreCheckDefinition", + "hash": "ccd79c897607dea1acf59714597d837f" + }, + { + "id": "1833c77a-7b1e-9dd9-353f-c51fbe90f4cd", + "state": "present", + "name": "TransformPython", + "namespace": "Core", + "description": "A transform function written in Python", + "label": "Transform Python", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:cog-transfer", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": "/topics/transformation", + "attributes": [ + { + "id": "1833c77a-7b7a-552d-353f-c51f78d366a0", + "state": "present", + "name": "file_path", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "File Path", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7bd3-8271-353b-c51fb4e8a305", + "state": "present", + "name": "class_name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Class Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "timeout", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Timeout", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 4000, + "default_value": 10, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-7c95-0dce-3534-c51a332dba03", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-7c35-6c09-3539-c5114aedade7", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "repository", + "peer": "CoreGenericRepository", + "kind": "Attribute", + "label": "Repository", + "description": null, + "identifier": "repository__transformation", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "query", + "peer": "CoreGraphQLQuery", + "kind": "Attribute", + "label": "Query", + "description": null, + "identifier": "graphql_query__transformation", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 5000, + "optional": false, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "tags", + "peer": "BuiltinTag", + "kind": "Attribute", + "label": "Tags", + "description": null, + "identifier": "builtintag__coretransformation", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTransformation" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreTransformPython", + "hash": "24939b3896c8ca208bdd97cc96b45e0d" + }, + { + "id": "1833c77a-7cfc-297a-353e-c5157a7e7c77", + "state": "present", + "name": "GraphQLQuery", + "namespace": "Core", + "description": "A pre-defined GraphQL Query", + "label": "GraphQL Query", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:graphql", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": "/topics/graphql", + "attributes": [ + { + "id": "1833c77a-7f24-72e4-353b-c51d30ab3938", + "state": "present", + "name": "models", + "kind": "List", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Models", + "description": "List of models associated with this query", + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 6000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7ec8-f5d5-3536-c51a184b7fc7", + "state": "present", + "name": "operations", + "kind": "List", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Operations", + "description": "Operations in use in the query, valid operations: 'query', 'mutation' or 'subscription'", + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 5000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7e0b-89ce-3531-c51d7e831517", + "state": "present", + "name": "query", + "kind": "TextArea", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Query", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7e67-85a8-3534-c519e63edbc7", + "state": "present", + "name": "variables", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Variables", + "description": "variables in use in the query", + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7db3-0cd0-3537-c518e57c4ebe", + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7f84-b384-3535-c510a7a9ef04", + "state": "present", + "name": "depth", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Depth", + "description": "number of nested levels in the query", + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 7000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7fe4-2640-353d-c51c62544848", + "state": "present", + "name": "height", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Height", + "description": "total number of fields requested in the query", + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 8000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-7d5a-3adb-353e-c518b97d2c02", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-8040-eb70-3537-c51c28ef60d8", + "state": "present", + "name": "repository", + "peer": "CoreGenericRepository", + "kind": "Attribute", + "label": "Repository", + "description": null, + "identifier": "graphql_query__repository", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-809c-7e25-3536-c519b3757861", + "state": "present", + "name": "tags", + "peer": "BuiltinTag", + "kind": "Attribute", + "label": "Tags", + "description": null, + "identifier": "builtintag__coregraphqlquery", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8153-9e07-3535-c51ab9ada569", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 12000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-80fa-212c-3539-c5148359cdae", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreGraphQLQuery", + "hash": "f6e7dc05e6e9982b8f9d958de64bd350" + }, + { + "id": "1833c77a-81b8-8662-3539-c5125d7a1aee", + "state": "present", + "name": "Artifact", + "namespace": "Core", + "description": null, + "label": "Artifact", + "branch": "local", + "default_filter": "name__value", + "human_friendly_id": null, + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:file-document-outline", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": null, + "documentation": "/topics/artifact", + "attributes": [ + { + "id": "1833c77a-82c7-267f-3537-c51956ca7541", + "state": "present", + "name": "content_type", + "kind": "Text", + "enum": [ + "application/json", + "application/yaml", + "application/xml", + "text/plain", + "text/markdown", + "text/csv", + "image/svg+xml" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Content Type", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "local", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-83df-d7eb-353d-c511c11b47e3", + "state": "present", + "name": "parameters", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Parameters", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 6000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8383-32cc-353d-c5101c34db4f", + "state": "present", + "name": "storage_id", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Storage Id", + "description": "ID of the file in the object store", + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 5000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8213-9900-3532-c51cd140e878", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "local", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8320-8a9c-3530-c51220af1bbc", + "state": "present", + "name": "checksum", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Checksum", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "local", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-826e-4b43-353c-c51026fc80ad", + "state": "present", + "name": "status", + "kind": "Text", + "enum": [ + "Error", + "Pending", + "Processing", + "Ready" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Status", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "local", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-843a-14d4-3538-c510bfb4d97d", + "state": "present", + "name": "object", + "peer": "CoreArtifactTarget", + "kind": "Attribute", + "label": "Object", + "description": null, + "identifier": "artifact__node", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 7000, + "optional": false, + "branch": "local", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-84e4-3102-3530-c51ac04b9c53", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8555-fa90-3530-c51e3c352dd1", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8490-5f00-3538-c51a01eb24a1", + "state": "present", + "name": "definition", + "peer": "CoreArtifactDefinition", + "kind": "Attribute", + "label": "Definition", + "description": null, + "identifier": "artifact__artifact_definition", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 8000, + "optional": false, + "branch": "local", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreArtifact", + "hash": "9b3370be81ed0925abdd63439ed5bdac" + }, + { + "id": "1833c77a-85b5-4cbf-353b-c5155cc14885", + "state": "present", + "name": "ArtifactDefinition", + "namespace": "Core", + "description": null, + "label": "Artifact Definition", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:file-document-multiple-outline", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": "/topics/artifact", + "attributes": [ + { + "id": "1833c77a-8711-08ed-3536-c51192324c58", + "state": "present", + "name": "parameters", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Parameters", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-876b-e26a-3539-c5166c1c3ed2", + "state": "present", + "name": "content_type", + "kind": "Text", + "enum": [ + "application/json", + "application/yaml", + "application/xml", + "text/plain", + "text/markdown", + "text/csv", + "image/svg+xml" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Content Type", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 5000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-860b-e657-3533-c519332ce4f9", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8662-c4f3-3537-c51f9474f797", + "state": "present", + "name": "artifact_name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Artifact Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-86ba-259a-3531-c518b487f8fb", + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-87c9-038f-353f-c51e2ccf9977", + "state": "present", + "name": "targets", + "peer": "CoreGroup", + "kind": "Attribute", + "label": "Targets", + "description": null, + "identifier": "artifact_definition___group", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8819-984e-3533-c518396e5b4e", + "state": "present", + "name": "transformation", + "peer": "CoreTransformation", + "kind": "Attribute", + "label": "Transformation", + "description": null, + "identifier": "artifact_definition___transformation", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 7000, + "optional": false, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-88c0-bb6a-3539-c51f5b3bd73a", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-886c-ec56-3537-c51dd46d209a", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreArtifactDefinition", + "hash": "42d3ec80278adbc2d1aba713e4ba4d63" + }, + { + "id": "1833c77a-8919-7a7d-353e-c51cd5a2ebd9", + "state": "present", + "name": "GeneratorDefinition", + "namespace": "Core", + "description": null, + "label": "Generator Definition", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:state-machine", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": "/topics/generator", + "attributes": [ + { + "id": "1833c77a-8b08-b358-353b-c51b8585c0c7", + "state": "present", + "name": "convert_query_response", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Convert Query Response", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 6000, + "default_value": false, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8ab7-bc75-3531-c51f3f14708f", + "state": "present", + "name": "class_name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Class Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 5000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-89bc-b9fe-3531-c5105d71b662", + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-896a-8759-3530-c51f2239f784", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8a12-0424-353c-c5184c2c018e", + "state": "present", + "name": "parameters", + "kind": "JSON", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Parameters", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8a67-193a-3538-c518a3e81a97", + "state": "present", + "name": "file_path", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "File Path", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-8b5d-e2a3-3538-c512bb743ae9", + "state": "present", + "name": "query", + "peer": "CoreGraphQLQuery", + "kind": "Attribute", + "label": "Query", + "description": null, + "identifier": "generator_definition__graphql_query", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 7000, + "optional": false, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8bb2-c738-3531-c518af7d96e0", + "state": "present", + "name": "repository", + "peer": "CoreGenericRepository", + "kind": "Attribute", + "label": "Repository", + "description": null, + "identifier": "generator_definition__repository", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 8000, + "optional": false, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8cc1-14f9-3539-c5142a1302f1", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 11000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8c6c-b91c-353d-c51b53164d56", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8c0e-10d9-3533-c5192411847d", + "state": "present", + "name": "targets", + "peer": "CoreGroup", + "kind": "Attribute", + "label": "Targets", + "description": null, + "identifier": "generator_definition___group", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 9000, + "optional": false, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreGeneratorDefinition", + "hash": "f3ca0b7240c9a7448329d3868e29ac43" + }, + { + "id": "1833c77a-8d1e-bf7d-3531-c51159b9cee0", + "state": "present", + "name": "GeneratorInstance", + "namespace": "Core", + "description": null, + "label": "Generator Instance", + "branch": "local", + "default_filter": "name__value", + "human_friendly_id": null, + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:file-document-outline", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": null, + "documentation": "/topics/generator", + "attributes": [ + { + "id": "1833c77a-8d74-ba65-3532-c51f865e18f2", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "local", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-8dce-95d0-3535-c51145a09c14", + "state": "present", + "name": "status", + "kind": "Text", + "enum": [ + "Error", + "Pending", + "Processing", + "Ready" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Status", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "local", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-8e27-737d-353f-c51bb374ab0b", + "state": "present", + "name": "object", + "peer": "CoreNode", + "kind": "Attribute", + "label": "Object", + "description": null, + "identifier": "generator__node", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 3000, + "optional": false, + "branch": "local", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8ecf-67e8-353e-c510b13b2afb", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8e7c-85f8-353d-c5104194a59a", + "state": "present", + "name": "definition", + "peer": "CoreGeneratorDefinition", + "kind": "Attribute", + "label": "Definition", + "description": null, + "identifier": "generator__generator_definition", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 4000, + "optional": false, + "branch": "local", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-8f4d-efff-3538-c5132bac95f3", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreGeneratorInstance", + "hash": "bc12eb9312356efce1a0af2dd5cfefd7" + }, + { + "id": "1833c77a-8faa-3e88-3539-c5111318486f", + "state": "present", + "name": "StandardWebhook", + "namespace": "Core", + "description": "A webhook that connects to an external integration", + "label": "Standard Webhook", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:webhook", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-9001-13b2-3539-c51cc08836d1", + "state": "present", + "name": "shared_key", + "kind": "Password", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Shared Key", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "node_kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Node Kind", + "description": "Only send node mutation events for nodes of this kind", + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2250, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "branch_scope", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "other_branches", + "description": "All branches except the default branch", + "color": "#e5e7eb", + "label": "Other Branches" + }, + { + "id": null, + "state": "present", + "name": "default_branch", + "description": "Only the default branch", + "color": "#86efac", + "label": "Default Branch" + }, + { + "id": null, + "state": "present", + "name": "all_branches", + "description": "All branches", + "color": "#fef08a", + "label": "All Branches" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Branch Scope", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "default_branch", + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validate_certificates", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Validate Certificates", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": true, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "url", + "kind": "URL", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Url", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2500, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "event_type", + "kind": "Text", + "enum": [ + "all", + "infrahub.branch.created", + "infrahub.branch.deleted", + "infrahub.branch.merged", + "infrahub.branch.rebased", + "infrahub.schema.updated", + "infrahub.node.created", + "infrahub.node.updated", + "infrahub.node.deleted", + "infrahub.group.member_added", + "infrahub.group.member_removed", + "infrahub.repository.update_commit", + "infrahub.artifact.created", + "infrahub.artifact.updated", + "infrahub.validator.started", + "infrahub.validator.passed", + "infrahub.validator.failed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Event Type", + "description": "The event type that triggers the webhook", + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1500, + "default_value": "all", + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-9053-e21a-3535-c519be0ba2bf", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-90ab-649c-353a-c51a18e69393", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreWebhook", + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreStandardWebhook", + "hash": "d699f53477d74b4d4b3a3d2f5a0b95fc" + }, + { + "id": "1833c77a-9113-6e12-353e-c513932c0680", + "state": "present", + "name": "CustomWebhook", + "namespace": "Core", + "description": "A webhook that connects to an external integration", + "label": "Custom Webhook", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:cog-outline", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "node_kind", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Node Kind", + "description": "Only send node mutation events for nodes of this kind", + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2250, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "branch_scope", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "other_branches", + "description": "All branches except the default branch", + "color": "#e5e7eb", + "label": "Other Branches" + }, + { + "id": null, + "state": "present", + "name": "default_branch", + "description": "Only the default branch", + "color": "#86efac", + "label": "Default Branch" + }, + { + "id": null, + "state": "present", + "name": "all_branches", + "description": "All branches", + "color": "#fef08a", + "label": "All Branches" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Branch Scope", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": "default_branch", + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "validate_certificates", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Validate Certificates", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": true, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "url", + "kind": "URL", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Url", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2500, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "event_type", + "kind": "Text", + "enum": [ + "all", + "infrahub.branch.created", + "infrahub.branch.deleted", + "infrahub.branch.merged", + "infrahub.branch.rebased", + "infrahub.schema.updated", + "infrahub.node.created", + "infrahub.node.updated", + "infrahub.node.deleted", + "infrahub.group.member_added", + "infrahub.group.member_removed", + "infrahub.repository.update_commit", + "infrahub.artifact.created", + "infrahub.artifact.updated", + "infrahub.validator.started", + "infrahub.validator.passed", + "infrahub.validator.failed" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Event Type", + "description": "The event type that triggers the webhook", + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1500, + "default_value": "all", + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-9216-b9a1-3538-c51ef9e16f38", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 10000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-91c2-62cf-3539-c512d2cac1e8", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-916b-5624-3536-c519b5950ec7", + "state": "present", + "name": "transformation", + "peer": "CoreTransformPython", + "kind": "Attribute", + "label": "Transformation", + "description": null, + "identifier": "webhook___transformation", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreWebhook", + "CoreTaskTarget" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreCustomWebhook", + "hash": "dfbab8307e355961197c3c09a2121398" + }, + { + "id": "1833c77a-9273-a2c9-353c-c5182b71726a", + "state": "present", + "name": "Namespace", + "namespace": "Ipam", + "description": "A namespace that segments IPAM", + "label": "IPAM Namespace", + "branch": "aware", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:format-list-group", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-92c9-b521-3538-c5111bb7bf04", + "state": "present", + "name": "default", + "kind": "Boolean", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Default", + "description": null, + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 9000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-93d5-9a58-3539-c5114915254a", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-9381-5cc1-3534-c51316629713", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-9323-be9b-353e-c511fa8e0e84", + "state": "present", + "name": "profiles", + "peer": "CoreProfile", + "kind": "Profile", + "label": "Profiles", + "description": null, + "identifier": "node__profile", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "ip_prefixes", + "peer": "BuiltinIPPrefix", + "kind": "Generic", + "label": "IP Prefixes", + "description": null, + "identifier": "ip_namespace__ip_prefix", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 3000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "none", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "ip_addresses", + "peer": "BuiltinIPAddress", + "kind": "Generic", + "label": "IP Addresses", + "description": null, + "identifier": "ip_namespace__ip_address", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 4000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "cascade", + "allow_override": "none", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "BuiltinIPNamespace" + ], + "generate_profile": true, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "IpamNamespace", + "hash": "3e64fca1fdc066fc9466a2718dfe043b" + }, + { + "id": "1833c77a-9431-6176-3534-c51c6f95eebd", + "state": "present", + "name": "IPPrefixPool", + "namespace": "Core", + "description": "A pool of IP prefix resources", + "label": "IP Prefix Pool", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:view-grid-outline", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-94d9-553b-3539-c51b217b4a74", + "state": "present", + "name": "default_member_type", + "kind": "Text", + "enum": [ + "prefix", + "address" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Default Member Type", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": "prefix", + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-952e-bf9b-353c-c515c6731c8b", + "state": "present", + "name": "default_prefix_type", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Default Prefix Type", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9484-2014-353d-c5122f82842a", + "state": "present", + "name": "default_prefix_length", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Default Prefix Length", + "description": "The default prefix length as an integer for prefixes allocated from this pool.", + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-9583-4235-3538-c51a1d4c8376", + "state": "present", + "name": "resources", + "peer": "BuiltinIPPrefix", + "kind": "Attribute", + "label": "Resources", + "description": null, + "identifier": "prefixpool__resource", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-9636-e33c-3534-c51a863df354", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-95e1-7bca-353d-c51de1ecdf59", + "state": "present", + "name": "ip_namespace", + "peer": "BuiltinIPNamespace", + "kind": "Attribute", + "label": "Ip Namespace", + "description": null, + "identifier": "prefixpool__ipnamespace", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 7000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-968c-6d82-353c-c515b45fc046", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreResourcePool", + "LineageSource" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreIPPrefixPool", + "hash": "e347813212c262ed20ef0b2dafcb1822" + }, + { + "id": "1833c77a-96ea-b2cf-3533-c51e1f8ed29a", + "state": "present", + "name": "IPAddressPool", + "namespace": "Core", + "description": "A pool of IP address resources", + "label": "IP Address Pool", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:view-grid-outline", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-979d-92f7-353c-c51f9c18aa6d", + "state": "present", + "name": "default_prefix_length", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Default Prefix Length", + "description": "The default prefix length as an integer for addresses allocated from this pool.", + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9744-62c5-3537-c515e68647fe", + "state": "present", + "name": "default_address_type", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Default Address Type", + "description": "The object type to create when reserving a resource in the pool", + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-98cf-4955-3535-c515ab3a0a1d", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-9876-7884-353f-c5137c4190d6", + "state": "present", + "name": "ip_namespace", + "peer": "BuiltinIPNamespace", + "kind": "Attribute", + "label": "Ip Namespace", + "description": null, + "identifier": "ipaddresspool__ipnamespace", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 6000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-994b-fda6-3539-c518753ee9eb", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-9817-ebed-3535-c5197862f537", + "state": "present", + "name": "resources", + "peer": "BuiltinIPPrefix", + "kind": "Attribute", + "label": "Resources", + "description": null, + "identifier": "ipaddresspool__resource", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": false, + "branch": "agnostic", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreResourcePool", + "LineageSource" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreIPAddressPool", + "hash": "d8500345652beaa8e02a54ebd2739aaa" + }, + { + "id": "1833c77a-99b3-7836-3530-c51160e36a90", + "state": "present", + "name": "NumberPool", + "namespace": "Core", + "description": "A pool of number resources", + "label": "Number Pool", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:view-grid-outline", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-9ac3-6501-3538-c5106d1d6a7f", + "state": "present", + "name": "start_range", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Start Range", + "description": "The start range for the pool", + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 5000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9a6d-fa4e-353f-c517448ac9ed", + "state": "present", + "name": "node_attribute", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Node Attribute", + "description": "The attribute of the selected model", + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 4000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9a16-cb21-3536-c51039a77db3", + "state": "present", + "name": "node", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Node", + "description": "The model of the object that requires integers to be allocated", + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9b1b-9e4a-3534-c516fc9dc18d", + "state": "present", + "name": "end_range", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "End Range", + "description": "The end range for the pool", + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 6000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-9bca-5e21-3532-c519c9bf8ba1", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-9b74-bdb8-3534-c51cee0a1c4b", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreResourcePool", + "LineageSource" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreNumberPool", + "hash": "f425d4f1258f8dd0eaffcfc584f01773" + }, + { + "id": "1833c77a-9c2b-6ec3-3536-c5194e4d2a91", + "state": "present", + "name": "GlobalPermission", + "namespace": "Core", + "description": "A permission that grants global rights to perform actions in Infrahub", + "label": "Global permission", + "branch": "agnostic", + "default_filter": null, + "human_friendly_id": [ + "action__value", + "decision__value" + ], + "display_labels": [ + "action__value", + "decision__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:user-key", + "order_by": [ + "action__value", + "decision__value" + ], + "uniqueness_constraints": [ + [ + "action__value", + "decision__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-9cdc-1fff-3537-c511d3f94b3a", + "state": "present", + "name": "decision", + "kind": "Number", + "enum": [ + 1, + 2, + 4, + 6 + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Decision", + "description": "Decide to deny or allow the action at a global level", + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": 6, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9c81-787a-353e-c511f2f0cd7f", + "state": "present", + "name": "action", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "super_admin", + "description": "", + "color": "#ed6a5a", + "label": "Super Admin" + }, + { + "id": null, + "state": "present", + "name": "override_context", + "description": "", + "color": "#f4f1bb", + "label": "Override Context" + }, + { + "id": null, + "state": "present", + "name": "merge_proposed_change", + "description": "", + "color": "#9bc1bc", + "label": "Merge Proposed Change" + }, + { + "id": null, + "state": "present", + "name": "merge_branch", + "description": "", + "color": "#5ca4a9", + "label": "Merge Branch" + }, + { + "id": null, + "state": "present", + "name": "manage_schema", + "description": "", + "color": "#e6ebe0", + "label": "Manage Schema" + }, + { + "id": null, + "state": "present", + "name": "manage_repositories", + "description": "", + "color": "#52489c", + "label": "Manage Repositories" + }, + { + "id": null, + "state": "present", + "name": "manage_permissions", + "description": "", + "color": "#4062bb", + "label": "Manage Permissions" + }, + { + "id": null, + "state": "present", + "name": "manage_accounts", + "description": "", + "color": "#59c3c3", + "label": "Manage Accounts" + }, + { + "id": null, + "state": "present", + "name": "edit_default_branch", + "description": "", + "color": "#56638a", + "label": "Edit Default Branch" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Action", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "identifier", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Identifier", + "description": null, + "read_only": true, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-9d97-0c60-3532-c51a73fc0314", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-9d3b-427d-353c-c513d86e9e9c", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "roles", + "peer": "CoreAccountRole", + "kind": "Attribute", + "label": "Roles", + "description": null, + "identifier": "role__permissions", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 3000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreBasePermission" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreGlobalPermission", + "hash": "c8b55a7344801ae27150b08df2b3aae3" + }, + { + "id": "1833c77a-9e15-83b3-353e-c510b36139c8", + "state": "present", + "name": "ObjectPermission", + "namespace": "Core", + "description": "A permission that grants rights to perform actions on objects", + "label": "Object permission", + "branch": "aware", + "default_filter": null, + "human_friendly_id": [ + "namespace__value", + "name__value", + "action__value", + "decision__value" + ], + "display_labels": [ + "namespace__value", + "name__value", + "action__value", + "decision__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:user-key", + "order_by": [ + "namespace__value", + "name__value", + "action__value", + "decision__value" + ], + "uniqueness_constraints": [ + [ + "namespace__value", + "name__value", + "action__value", + "decision__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-9ecb-af71-3538-c5156efab14c", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 3000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9e6e-c719-3531-c512d655d423", + "state": "present", + "name": "namespace", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Namespace", + "description": null, + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9f28-9216-3536-c513ebc0645b", + "state": "present", + "name": "action", + "kind": "Text", + "enum": [ + "any", + "create", + "update", + "delete", + "view" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Action", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 4000, + "default_value": "any", + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c77a-9f86-bbe2-3535-c514d88aeb98", + "state": "present", + "name": "decision", + "kind": "Number", + "enum": [ + 1, + 2, + 4, + 6 + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Decision", + "description": "Decide to deny or allow the action.If allowed, it can be configured for the default branch, any other branches or all branches", + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 5000, + "default_value": 6, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "identifier", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Identifier", + "description": null, + "read_only": true, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "none", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-9fe0-2dab-3539-c51ded8c871f", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-a040-2b66-353d-c51e791b23e4", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "roles", + "peer": "CoreAccountRole", + "kind": "Attribute", + "label": "Roles", + "description": null, + "identifier": "role__permissions", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 3000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "CoreBasePermission" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreObjectPermission", + "hash": "b0b38f562a3b1fc0b2c33f6f04722416" + }, + { + "id": "1833c77a-a0a0-6fee-353a-c51fbf1842f9", + "state": "present", + "name": "AccountRole", + "namespace": "Core", + "description": "A role defines a set of permissions to grant to a group of accounts", + "label": "Account role", + "branch": "aware", + "default_filter": null, + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:user-badge", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c77a-a0f9-5089-353b-c513a5910461", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-a294-3292-3535-c5199e173f19", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-a153-0273-3532-c5191147288c", + "state": "present", + "name": "groups", + "peer": "CoreAccountGroup", + "kind": "Attribute", + "label": "Groups", + "description": null, + "identifier": "role__accountgroups", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 2000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-a1b0-dcd1-3533-c512f63e4b63", + "state": "present", + "name": "permissions", + "peer": "CoreBasePermission", + "kind": "Attribute", + "label": "Permissions", + "description": null, + "identifier": "role__permissions", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 3000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-a20c-356b-353e-c517f9f1aef3", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 4000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [], + "generate_profile": false, + "generate_template": false, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "CoreAccountRole", + "hash": "2c582f0556177f98650ac3d9b67e552f" + }, + { + "id": "1833c77a-a319-f6b6-353f-c51803986e5f", + "state": "present", + "name": "AccountGroup", + "namespace": "Core", + "description": "A group of users to manage common permissions", + "label": "Account group", + "branch": "agnostic", + "default_filter": "name__value", + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:account-group", + "order_by": [ + "name__value" + ], + "uniqueness_constraints": [ + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": null, + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": false, + "unique": true, + "optional": false, + "branch": "agnostic", + "order_weight": 1000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "label", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Label", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 2000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "group_type", + "kind": "Text", + "enum": [ + "default", + "internal" + ], + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Group Type", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 4000, + "default_value": "default", + "inherited": true, + "allow_override": "any", + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "agnostic", + "order_weight": 3000, + "default_value": null, + "inherited": true, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c77a-a3ea-3e0c-353f-c512952f82e9", + "state": "present", + "name": "parent", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Parent", + "description": null, + "identifier": "parent__child", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "outbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-a44f-a9ea-3539-c5132898898a", + "state": "present", + "name": "children", + "peer": "CoreGroup", + "kind": "Hierarchy", + "label": "Children", + "description": null, + "identifier": "parent__child", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "inbound", + "hierarchical": "CoreGroup", + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c77a-a387-a676-3536-c515275655e1", + "state": "present", + "name": "roles", + "peer": "CoreAccountRole", + "kind": "Attribute", + "label": "Roles", + "description": null, + "identifier": "role__accountgroups", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "members", + "peer": "CoreNode", + "kind": "Generic", + "label": "Members", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 5000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": null, + "state": "present", + "name": "subscribers", + "peer": "CoreNode", + "kind": "Generic", + "label": "Subscribers", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": true, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [ + "LineageOwner", + "LineageSource", + "CoreGroup" + ], + "generate_profile": false, + "generate_template": false, + "hierarchy": "CoreGroup", + "parent": "CoreGroup", + "children": "CoreGroup", + "kind": "CoreAccountGroup", + "hash": "1538f03104a0d68aec4036d053d09839" + }, + { + "id": "1833c7eb-96e5-7b31-3536-c5179bcb2a8c", + "state": "present", + "name": "Device", + "namespace": "Infra", + "description": null, + "label": "Device", + "branch": "aware", + "default_filter": null, + "human_friendly_id": [ + "name__value" + ], + "display_labels": [ + "name__value" + ], + "include_in_menu": true, + "menu_placement": null, + "icon": "gis:search-country", + "order_by": null, + "uniqueness_constraints": [ + [ + "location", + "role__value", + "role_id__value" + ], + [ + "name__value" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c7eb-9ed0-521f-353a-c5104c94cadb", + "state": "present", + "name": "description", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Description", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 2000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c8ff-ae78-59dd-353a-c511c91a4d61", + "state": "present", + "name": "role_id", + "kind": "Number", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Role Id", + "description": "Id of the device for a given role on a given location", + "read_only": false, + "unique": false, + "optional": false, + "branch": "aware", + "order_weight": 1000, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c7eb-9f4b-39d4-353b-c51a4451c6ac", + "state": "present", + "name": "role", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "core", + "description": "", + "color": "#ed6a5a", + "label": "Core" + }, + { + "id": null, + "state": "present", + "name": "aggregation", + "description": "", + "color": "#f4f1bb", + "label": "Aggregation" + }, + { + "id": null, + "state": "present", + "name": "access", + "description": "", + "color": "#9bc1bc", + "label": "Access" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Role", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 3000, + "default_value": "core", + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c7eb-99e1-e016-3534-c511aeb5291f", + "state": "present", + "name": "name", + "kind": "Text", + "enum": null, + "computed_attribute": { + "kind": "Jinja2", + "jinja2_template": "{{ role__value }}{{ '%03d' % role_id__value }}-{{ location__shortname__value }}", + "transform": null + }, + "choices": null, + "regex": null, + "max_length": null, + "min_length": null, + "label": "Name", + "description": null, + "read_only": true, + "unique": true, + "optional": false, + "branch": "aware", + "order_weight": 1300, + "default_value": null, + "inherited": false, + "allow_override": "any", + "deprecation": null + }, + { + "id": "1833c7eb-9fcf-2dc6-353f-c5191cb5c102", + "state": "present", + "name": "status", + "kind": "Dropdown", + "enum": null, + "computed_attribute": null, + "choices": [ + { + "id": null, + "state": "present", + "name": "provisioning", + "description": "", + "color": "#ed6a5a", + "label": "Provisioning" + }, + { + "id": null, + "state": "present", + "name": "maintenance", + "description": "", + "color": "#f4f1bb", + "label": "Maintenance" + }, + { + "id": null, + "state": "present", + "name": "active", + "description": "", + "color": "#9bc1bc", + "label": "Active" + } + ], + "regex": null, + "max_length": null, + "min_length": null, + "label": "Status", + "description": null, + "read_only": false, + "unique": false, + "optional": true, + "branch": "aware", + "order_weight": 4000, + "default_value": "active", + "inherited": false, + "allow_override": "any", + "deprecation": null + } + ], + "relationships": [ + { + "id": "1833c7eb-a048-4f0d-353d-c5118813e40f", + "state": "present", + "name": "location", + "peer": "LocationSite", + "kind": "Generic", + "label": "Location", + "description": null, + "identifier": "infradevice__locationsite", + "cardinality": "one", + "min_count": 1, + "max_count": 1, + "order_weight": 5000, + "optional": false, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c7eb-a58a-6392-3531-c513fb9d4879", + "state": "present", + "name": "profiles", + "peer": "CoreProfile", + "kind": "Profile", + "label": "Profiles", + "description": null, + "identifier": "node__profile", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 7000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c7eb-a511-94d0-3531-c518cbca8b0b", + "state": "present", + "name": "primary_ip", + "peer": "IpamIPAddress", + "kind": "Generic", + "label": "Primary Ip", + "description": null, + "identifier": "infradevice__ipamipaddress", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 6000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c7eb-a60c-fb36-353b-c5165ae31278", + "state": "present", + "name": "member_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Member Of Groups", + "description": null, + "identifier": "group_member", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 8000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833f419-14f9-fdce-3533-c5159faf4c00", + "state": "present", + "name": "object_template", + "peer": "TemplateInfraDevice", + "kind": "Template", + "label": "Object Template", + "description": null, + "identifier": "node__objecttemplate", + "cardinality": "one", + "min_count": 0, + "max_count": 1, + "order_weight": 1, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + }, + { + "id": "1833c7eb-a686-9f05-3538-c516b34611af", + "state": "present", + "name": "subscriber_of_groups", + "peer": "CoreGroup", + "kind": "Group", + "label": "Subscriber Of Groups", + "description": null, + "identifier": "group_subscriber", + "cardinality": "many", + "min_count": 0, + "max_count": 0, + "order_weight": 9000, + "optional": true, + "branch": "aware", + "inherited": false, + "direction": "bidirectional", + "hierarchical": null, + "on_delete": "no-action", + "allow_override": "any", + "read_only": false, + "deprecation": null + } + ], + "inherit_from": [], + "generate_profile": true, + "generate_template": true, + "hierarchy": null, + "parent": null, + "children": null, + "kind": "InfraDevice", + "hash": "16fae1e61f5b7649703410c09e49c1da" + }, + { + "id": "1833c7eb-a704-ce92-3539-c5180e5e6c4c", + "state": "present", + "name": "IPAddress", + "namespace": "Ipam", + "description": "IP Address", + "label": "IP Address", + "branch": "aware", + "default_filter": "address__value", + "human_friendly_id": [ + "address__value", + "ip_namespace__name__value" + ], + "display_labels": [ + "address__value" + ], + "include_in_menu": false, + "menu_placement": null, + "icon": "mdi:ip", + "order_by": [ + "address__value" + ], + "uniqueness_constraints": [ + [ + "ip_namespace", + "address__value" + ], + [ + "address__value", + "ip_namespace" + ] + ], + "documentation": null, + "attributes": [ + { + "id": "1833c7eb-aa0e-bd1f-3534-c51e6254d901", + "state": "present", + "name": "fqdn", + "kind": "Text", + "enum": null, + "computed_attribute": null, + "choices": null, + "regex": "(?=^.{1,253}$)(^(((?!-)[a-zA-Z0-9-]{1,63}(? ObjectFile: + files = ObjectFile.load_from_disk(paths=[get_fixtures_dir() / "spec_objects" / name]) + assert len(files) == 1 + return files[0] + + +def load_menu_file(name: str) -> MenuFile: + files = MenuFile.load_from_disk(paths=[get_fixtures_dir() / "spec_objects" / name]) + assert len(files) == 1 + return files[0] + + +class TestSpecObject(TestInfrahubDockerClient, SchemaAnimal): + @pytest.fixture(scope="class") + def branch_name(self) -> str: + return "branch2" + + @pytest.fixture(scope="class") + def spec_objects_fixtures_dir(self) -> Path: + return get_fixtures_dir() / "spec_objects" + + @pytest.fixture(scope="class") + async def initial_schema(self, default_branch: str, client: InfrahubClient, schema_base: SchemaRoot) -> None: + await client.schema.wait_until_converged(branch=default_branch) + + resp = await client.schema.load( + schemas=[schema_base.to_schema_dict()], branch=default_branch, wait_until_converged=True + ) + assert resp.errors == {} + + async def test_create_branch(self, client: InfrahubClient, initial_schema: None, branch_name: str): + await client.branch.create(branch_name=branch_name, sync_with_git=False) + + async def test_load_tags(self, client: InfrahubClient, branch_name: str, initial_schema: None): + obj_file = load_object_file("animal_tags01.yml") + await obj_file.validate_format(client=client, branch=branch_name) + + # Check that the nodes are not present in the database before loading the file + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 0 + + await obj_file.process(client=client, branch=branch_name) + + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 3 + + async def test_update_tags(self, client: InfrahubClient, branch_name: str, initial_schema: None): + obj_file = load_object_file("animal_tags02.yml") + await obj_file.validate_format(client=client, branch=branch_name) + + # Check that the nodes are not present in the database before loading the file + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 3 + + await obj_file.process(client=client, branch=branch_name) + + tags = await client.all(kind=obj_file.spec.kind, branch=branch_name) + tags_by_name = {"__".join(tag.get_human_friendly_id()): tag for tag in tags} + assert len(tags_by_name) == 4 + assert tags_by_name["Veterinarian"].description.value == "Licensed animal healthcare professional" + + async def test_load_persons(self, client: InfrahubClient, branch_name: str, initial_schema: None): + obj_file = load_object_file("animal_person01.yml") + await obj_file.validate_format(client=client, branch=branch_name) + + # Check that the nodes are not present in the database before loading the file + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 0 + + await obj_file.process(client=client, branch=branch_name) + + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 3 + + async def test_load_dogs(self, client: InfrahubClient, branch_name: str, initial_schema: None): + obj_file = load_object_file("animal_dog01.yml") + await obj_file.validate_format(client=client, branch=branch_name) + + # Check that the nodes are not present in the database before loading the file + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 0 + + await obj_file.process(client=client, branch=branch_name) + + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 4 + + async def test_load_persons02(self, client: InfrahubClient, branch_name: str, initial_schema: None): + obj_file = load_object_file("animal_person02.yml") + await obj_file.validate_format(client=client, branch=branch_name) + + # Check that the nodes are not present in the database before loading the file + assert len(await client.all(kind=obj_file.spec.kind, branch=branch_name)) == 4 + + await obj_file.process(client=client, branch=branch_name) + + persons = await client.all(kind=obj_file.spec.kind, branch=branch_name) + person_by_name = {"__".join(person.get_human_friendly_id()): person for person in persons} + assert len(persons) == 6 + + # Validate that the best_friends relationship is correctly populated + await person_by_name["Mike Johnson"].best_friends.fetch() + friends = [peer.hfid for peer in person_by_name["Mike Johnson"].best_friends.peers] + assert friends == [["Jane Smith", "Max"], ["Sarah Williams", "Charlie"]] + + # Validate the tags relationship is correctly populated for both Alex Thompson and Mike Johnson + await person_by_name["Alex Thompson"].tags.fetch() + tags_alex = [tag.hfid for tag in person_by_name["Alex Thompson"].tags.peers] + assert tags_alex == [["Dog Lover"]] + + await person_by_name["Mike Johnson"].tags.fetch() + tags_mike = [tag.hfid for tag in person_by_name["Mike Johnson"].tags.peers] + assert sorted(tags_mike) == sorted([["Veterinarian"], ["Breeder"]]) + + # Validate that animals for Emily Parler have been correctly created + await person_by_name["Emily Parker"].animals.fetch() + animals_emily = [animal.display_label for animal in person_by_name["Emily Parker"].animals.peers] + assert sorted(animals_emily) == sorted(["Max Golden Retriever", "Whiskers Siamese #FFD700"]) + + async def test_load_menu(self, client: InfrahubClient, branch_name: str, initial_schema: None): + menu_file = load_menu_file("animal_menu01.yml") + await menu_file.validate_format(client=client, branch=branch_name) + + await menu_file.process(client=client, branch=branch_name) + + menu = await client.filters(kind=menu_file.spec.kind, protected__value=False, branch=branch_name) + assert len(menu) == 3 + + menu_by_name = {menu.display_label: menu for menu in menu} + await menu_by_name["Animals"].children.fetch() + peer_labels = [peer.display_label for peer in menu_by_name["Animals"].children.peers] + assert sorted(peer_labels) == sorted(["Dog", "Cat"]) diff --git a/tests/unit/ctl/conftest.py b/tests/unit/ctl/conftest.py index ea4ce43a..e63efec1 100644 --- a/tests/unit/ctl/conftest.py +++ b/tests/unit/ctl/conftest.py @@ -1,9 +1,28 @@ import pytest +import ujson from pytest_httpx import HTTPXMock +from infrahub_sdk.utils import get_fixtures_dir from tests.unit.sdk.conftest import mock_query_infrahub_user, mock_query_infrahub_version # noqa: F401 +@pytest.fixture +async def schema_query_05_data() -> dict: + response_text = (get_fixtures_dir() / "schema_05.json").read_text(encoding="UTF-8") + return ujson.loads(response_text) + + +@pytest.fixture +async def mock_schema_query_05(httpx_mock: HTTPXMock, schema_query_05_data: dict) -> HTTPXMock: + httpx_mock.add_response( + method="GET", + url="http://mock/api/schema?branch=main", + json=schema_query_05_data, + is_reusable=True, + ) + return httpx_mock + + @pytest.fixture async def mock_branches_list_query(httpx_mock: HTTPXMock) -> HTTPXMock: response = { diff --git a/tests/unit/ctl/test_cli.py b/tests/unit/ctl/test_cli.py index 30d63e92..3aa56948 100644 --- a/tests/unit/ctl/test_cli.py +++ b/tests/unit/ctl/test_cli.py @@ -1,9 +1,12 @@ +import pytest from typer.testing import CliRunner from infrahub_sdk.ctl.cli import app runner = CliRunner() +pytestmark = pytest.mark.httpx_mock(can_send_already_matched_responses=True) + def test_main_app(): result = runner.invoke(app, ["--help"]) diff --git a/tests/unit/ctl/test_menu_app.py b/tests/unit/ctl/test_menu_app.py new file mode 100644 index 00000000..9b09949b --- /dev/null +++ b/tests/unit/ctl/test_menu_app.py @@ -0,0 +1,36 @@ +from pytest_httpx import HTTPXMock +from typer.testing import CliRunner + +from infrahub_sdk.ctl.menu import app +from infrahub_sdk.ctl.utils import get_fixtures_dir +from tests.helpers.cli import remove_ansi_color + +runner = CliRunner() + + +def test_menu_validate_bad_yaml() -> None: + fixture_file = get_fixtures_dir() / "menus" / "invalid_yaml.yml" + result = runner.invoke(app=app, args=["load", str(fixture_file)]) + + assert result.exit_code == 1 + assert "Invalid YAML/JSON file" in result.stdout + + +def test_menu_validate_valid(mock_schema_query_05: HTTPXMock) -> None: + fixture_file = get_fixtures_dir() / "menus" / "valid_menu.yml" + + result = runner.invoke(app=app, args=["validate", str(fixture_file)]) + + assert result.exit_code == 0 + assert f"File '{fixture_file}' is Valid!" in remove_ansi_color(result.stdout.replace("\n", "")) + + +def test_menu_validate_invalid(mock_schema_query_05: HTTPXMock) -> None: + fixture_file = get_fixtures_dir() / "menus" / "invalid_menu.yml" + + result = runner.invoke(app=app, args=["validate", str(fixture_file)]) + + assert result.exit_code == 1 + assert f"File '{fixture_file}' is not valid! 1.namespace: namespace is mandatory" in remove_ansi_color( + result.stdout.replace("\n", "") + ) diff --git a/tests/unit/ctl/test_schema_app.py b/tests/unit/ctl/test_schema_app.py index f3db9cae..9949eef7 100644 --- a/tests/unit/ctl/test_schema_app.py +++ b/tests/unit/ctl/test_schema_app.py @@ -14,7 +14,7 @@ def test_schema_load_empty(httpx_mock: HTTPXMock): result = runner.invoke(app=app, args=["load", str(fixture_file)]) assert result.exit_code == 1 - assert "Empty YAML/JSON file" in result.stdout + assert "Invalid YAML/JSON file" in result.stdout def test_schema_load_one_valid(httpx_mock: HTTPXMock): diff --git a/tests/unit/ctl/test_validate_app.py b/tests/unit/ctl/test_validate_app.py index 43d41ddb..c732d3e8 100644 --- a/tests/unit/ctl/test_validate_app.py +++ b/tests/unit/ctl/test_validate_app.py @@ -21,7 +21,7 @@ def test_validate_schema_empty(): result = runner.invoke(app=app, args=["schema", str(fixture_file)]) assert result.exit_code == 1 - assert "Empty YAML/JSON file" in remove_ansi_color(result.stdout) + assert "Invalid YAML/JSON file" in remove_ansi_color(result.stdout) def test_validate_schema_non_valid(): diff --git a/tests/unit/sdk/checks/conftest.py b/tests/unit/sdk/checks/conftest.py index 8aada87d..4fa6d1cc 100644 --- a/tests/unit/sdk/checks/conftest.py +++ b/tests/unit/sdk/checks/conftest.py @@ -17,5 +17,6 @@ async def mock_gql_query_my_query(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=response, url="http://localhost:8000/api/query/my_query?branch=main&update_group=false", + is_reusable=True, ) return httpx_mock diff --git a/tests/unit/sdk/checks/test_checks.py b/tests/unit/sdk/checks/test_checks.py index 6dcfbea4..6cad162b 100644 --- a/tests/unit/sdk/checks/test_checks.py +++ b/tests/unit/sdk/checks/test_checks.py @@ -3,6 +3,8 @@ from infrahub_sdk import InfrahubClient from infrahub_sdk.checks import InfrahubCheck +pytestmark = pytest.mark.httpx_mock(can_send_already_matched_responses=True) + async def test_class_init(): class IFCheckNoQuery(InfrahubCheck): diff --git a/tests/unit/sdk/conftest.py b/tests/unit/sdk/conftest.py index afb9250c..6943900e 100644 --- a/tests/unit/sdk/conftest.py +++ b/tests/unit/sdk/conftest.py @@ -18,6 +18,8 @@ if TYPE_CHECKING: from pytest_httpx import HTTPXMock +pytestmark = pytest.mark.httpx_mock(can_send_already_matched_responses=True) + @dataclass class BothClients: @@ -1467,6 +1469,7 @@ async def mock_branches_list_query(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-branch-all"}, + is_reusable=True, ) return httpx_mock @@ -1500,8 +1503,8 @@ async def mock_repositories_query_no_pagination(httpx_mock: HTTPXMock) -> HTTPXM } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1) - httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1, is_reusable=True) + httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2, is_reusable=True) return httpx_mock @@ -1534,6 +1537,7 @@ async def mock_query_repository_all_01_no_pagination( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-repository-all"}, + is_reusable=True, ) return httpx_mock @@ -1599,8 +1603,8 @@ async def mock_repositories_query(httpx_mock: HTTPXMock) -> HTTPXMock: } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1) - httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1, is_reusable=True) + httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2, is_reusable=True) return httpx_mock @@ -1640,6 +1644,7 @@ async def mock_query_repository_page1_1( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) return httpx_mock @@ -1674,13 +1679,14 @@ async def mock_query_corenode_page1_1(httpx_mock: HTTPXMock, client: InfrahubCli method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corenode-page1"}, + is_reusable=True, ) return httpx_mock @pytest.fixture async def mock_query_repository_count(httpx_mock: HTTPXMock, client: InfrahubClient, mock_schema_query_01) -> HTTPXMock: - httpx_mock.add_response(method="POST", json={"data": {"CoreRepository": {"count": 5}}}) + httpx_mock.add_response(method="POST", json={"data": {"CoreRepository": {"count": 5}}}, is_reusable=True) return httpx_mock @@ -1694,6 +1700,7 @@ async def mock_query_repository_page1_empty( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) return httpx_mock @@ -1743,6 +1750,7 @@ async def mock_query_repository_page1_2( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) return httpx_mock @@ -1783,34 +1791,71 @@ async def mock_query_repository_page2_2( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page2"}, + is_reusable=True, ) return httpx_mock @pytest.fixture -async def mock_schema_query_01(httpx_mock: HTTPXMock) -> HTTPXMock: +async def schema_query_01_data() -> dict: response_text = (get_fixtures_dir() / "schema_01.json").read_text(encoding="UTF-8") + return ujson.loads(response_text) + + +@pytest.fixture +async def schema_query_02_data() -> dict: + response_text = (get_fixtures_dir() / "schema_02.json").read_text(encoding="UTF-8") + return ujson.loads(response_text) + + +@pytest.fixture +async def schema_query_04_data() -> dict: + response_text = (get_fixtures_dir() / "schema_04.json").read_text(encoding="UTF-8") + return ujson.loads(response_text) + +@pytest.fixture +async def schema_query_05_data() -> dict: + response_text = (get_fixtures_dir() / "schema_05.json").read_text(encoding="UTF-8") + return ujson.loads(response_text) + + +@pytest.fixture +async def mock_schema_query_01(httpx_mock: HTTPXMock, schema_query_01_data: dict) -> HTTPXMock: httpx_mock.add_response( method="GET", url="http://mock/api/schema?branch=main", - json=ujson.loads(response_text), + json=schema_query_01_data, + is_reusable=True, ) return httpx_mock @pytest.fixture -async def mock_schema_query_02(httpx_mock: HTTPXMock) -> HTTPXMock: - response_text = (get_fixtures_dir() / "schema_02.json").read_text(encoding="UTF-8") +async def mock_schema_query_02(httpx_mock: HTTPXMock, schema_query_02_data: dict) -> HTTPXMock: + httpx_mock.add_response( + method="GET", + url=re.compile(r"^http://mock/api/schema\?branch=(main|cr1234)"), + json=schema_query_02_data, + is_reusable=True, + ) + return httpx_mock + + +@pytest.fixture +async def mock_schema_query_05(httpx_mock: HTTPXMock, schema_query_05_data: dict) -> HTTPXMock: httpx_mock.add_response( - method="GET", url=re.compile(r"^http://mock/api/schema\?branch=(main|cr1234)"), json=ujson.loads(response_text) + method="GET", + url="http://mock/api/schema?branch=main", + json=schema_query_05_data, + is_reusable=True, ) return httpx_mock @pytest.fixture async def mock_rest_api_artifact_definition_generate(httpx_mock: HTTPXMock) -> HTTPXMock: - httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*")) + httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*"), is_reusable=True) return httpx_mock @@ -1822,6 +1867,7 @@ async def mock_rest_api_artifact_fetch(httpx_mock: HTTPXMock) -> HTTPXMock: method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(schema_response), + is_reusable=True, ) graphql_response = { @@ -1902,18 +1948,19 @@ async def mock_rest_api_artifact_fetch(httpx_mock: HTTPXMock) -> HTTPXMock: ip name-server 1.1.1.1 """ - httpx_mock.add_response(method="GET", url=re.compile(r"^http://mock/api/storage/object/.*"), text=artifact_content) + httpx_mock.add_response( + method="GET", url=re.compile(r"^http://mock/api/storage/object/.*"), text=artifact_content, is_reusable=True + ) return httpx_mock @pytest.fixture -async def mock_rest_api_artifact_generate(httpx_mock: HTTPXMock) -> HTTPXMock: - schema_response = (get_fixtures_dir() / "schema_04.json").read_text(encoding="UTF-8") - +async def mock_rest_api_artifact_generate(httpx_mock: HTTPXMock, schema_query_04_data: dict) -> HTTPXMock: httpx_mock.add_response( method="GET", url="http://mock/api/schema?branch=main", - json=ujson.loads(schema_response), + json=schema_query_04_data, + is_reusable=True, ) artifact_graphql_response = { @@ -1987,7 +2034,9 @@ async def mock_rest_api_artifact_generate(httpx_mock: HTTPXMock) -> HTTPXMock: }, } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=artifact_graphql_response) + httpx_mock.add_response( + method="POST", url="http://mock/graphql/main", json=artifact_graphql_response, is_reusable=True + ) artifact_definition_graphql_response = { "data": { @@ -2090,32 +2139,34 @@ async def mock_rest_api_artifact_generate(httpx_mock: HTTPXMock) -> HTTPXMock: } } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=artifact_definition_graphql_response) - httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*")) + httpx_mock.add_response( + method="POST", url="http://mock/graphql/main", json=artifact_definition_graphql_response, is_reusable=True + ) + httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*"), is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_dropdown_add(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaDropdownAdd": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_dropdown_remove(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaDropdownRemove": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_enum_add(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaEnumAdd": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_enum_remove(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaEnumRemove": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture @@ -2134,21 +2185,21 @@ async def mock_query_mutation_location_create_failed(httpx_mock: HTTPXMock) -> H ], } url_regex = re.compile(r"http://mock/graphql/main") - httpx_mock.add_response(method="POST", url=url_regex, json=response1) - httpx_mock.add_response(method="POST", url=url_regex, json=response2) + httpx_mock.add_response(method="POST", url=url_regex, json=response1, is_reusable=True) + httpx_mock.add_response(method="POST", url=url_regex, json=response2, is_reusable=True) return httpx_mock @pytest.fixture async def mock_query_infrahub_version(httpx_mock: HTTPXMock) -> HTTPXMock: - httpx_mock.add_response(method="POST", json={"data": {"InfrahubInfo": {"version": "1.1.0"}}}) + httpx_mock.add_response(method="POST", json={"data": {"InfrahubInfo": {"version": "1.1.0"}}}, is_reusable=True) return httpx_mock @pytest.fixture async def mock_query_infrahub_user(httpx_mock: HTTPXMock) -> HTTPXMock: response_text = (get_fixtures_dir() / "account_profile.json").read_text(encoding="UTF-8") - httpx_mock.add_response(method="POST", json=ujson.loads(response_text)) + httpx_mock.add_response(method="POST", json=ujson.loads(response_text), is_reusable=True) return httpx_mock @@ -2483,7 +2534,9 @@ def query_introspection() -> str: async def mock_schema_query_ipam(httpx_mock: HTTPXMock) -> HTTPXMock: response_text = (get_fixtures_dir() / "schema_ipam.json").read_text(encoding="UTF-8") - httpx_mock.add_response(method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(response_text)) + httpx_mock.add_response( + method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(response_text), is_reusable=True + ) return httpx_mock @@ -2492,7 +2545,7 @@ async def mock_query_location_batch_count( httpx_mock: HTTPXMock, client: InfrahubClient, mock_schema_query_01 ) -> HTTPXMock: response = {"data": {"BuiltinLocation": {"count": 30}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) return httpx_mock @@ -2505,6 +2558,7 @@ async def mock_query_location_batch(httpx_mock: HTTPXMock, client: InfrahubClien method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": f"query-builtinlocation-page{i}"}, + is_reusable=True, ) return httpx_mock @@ -2518,6 +2572,7 @@ async def mock_query_tasks_01(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": f"query-tasks-page{i}"}, + is_reusable=True, ) return httpx_mock @@ -2530,6 +2585,7 @@ async def mock_query_tasks_02_main(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock @@ -2542,6 +2598,7 @@ async def mock_query_tasks_empty(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock @@ -2554,6 +2611,7 @@ async def mock_query_tasks_03(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock @@ -2567,6 +2625,7 @@ async def mock_query_tasks_04_full(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": f"query-tasks-page{i}"}, + is_reusable=True, ) return httpx_mock @@ -2579,5 +2638,6 @@ async def mock_query_tasks_05(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock diff --git a/tests/unit/sdk/spec/test_object.py b/tests/unit/sdk/spec/test_object.py new file mode 100644 index 00000000..f097b119 --- /dev/null +++ b/tests/unit/sdk/spec/test_object.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from infrahub_sdk.exceptions import ValidationError +from infrahub_sdk.spec.object import ObjectFile, RelationshipDataFormat, get_relationship_info + +if TYPE_CHECKING: + from pytest_httpx import HTTPXMock + + from infrahub_sdk.client import InfrahubClient + + +@pytest.fixture +def root_location() -> dict: + return {"apiVersion": "infrahub.app/v1", "kind": "Object", "spec": {"kind": "BuiltinLocation", "data": []}} + + +@pytest.fixture +def location_mexico_01(root_location: dict) -> dict: + data = [{"name": "Mexico", "type": "Country"}] + + location = root_location.copy() + location["spec"]["data"] = data + return location + + +@pytest.fixture +def location_bad_syntax01(root_location: dict) -> dict: + data = [{"notthename": "Mexico", "type": "Country"}] + location = root_location.copy() + location["spec"]["data"] = data + return location + + +@pytest.fixture +def location_bad_syntax02(root_location: dict) -> dict: + data = [{"name": "Mexico", "notvalidattribute": "notvalidattribute", "type": "Country"}] + location = root_location.copy() + location["spec"]["data"] = data + return location + + +async def test_validate_object(client: InfrahubClient, mock_schema_query_01: HTTPXMock, location_mexico_01): + obj = ObjectFile(location="some/path", content=location_mexico_01) + await obj.validate_format(client=client) + + assert obj.spec.kind == "BuiltinLocation" + + +async def test_validate_object_bad_syntax01( + client: InfrahubClient, mock_schema_query_01: HTTPXMock, location_bad_syntax01 +): + obj = ObjectFile(location="some/path", content=location_bad_syntax01) + with pytest.raises(ValidationError) as exc: + await obj.validate_format(client=client) + + assert "name" in str(exc.value) + + +async def test_validate_object_bad_syntax02( + client: InfrahubClient, mock_schema_query_01: HTTPXMock, location_bad_syntax02 +): + obj = ObjectFile(location="some/path", content=location_bad_syntax02) + with pytest.raises(ValidationError) as exc: + await obj.validate_format(client=client) + + assert "notvalidattribute" in str(exc.value) + + +get_relationship_info_testdata = [ + pytest.param( + [ + {"data": {"name": "Blue"}}, + {"data": {"name": "Red"}}, + ], + True, + RelationshipDataFormat.MANY_OBJ_LIST_DICT, + id="many_obj_list_dict", + ), + pytest.param( + { + "data": [ + {"name": "Blue"}, + {"name": "Red"}, + ], + }, + True, + RelationshipDataFormat.MANY_OBJ_DICT_LIST, + id="many_obj_dict_list", + ), + pytest.param( + ["blue", "red"], + True, + RelationshipDataFormat.MANY_REF, + id="many_ref", + ), + pytest.param( + [ + {"name": "Blue"}, + {"name": "Red"}, + ], + False, + RelationshipDataFormat.UNKNOWN, + id="many_invalid_list_dict", + ), +] + + +@pytest.mark.parametrize("data,is_valid,format", get_relationship_info_testdata) +async def test_get_relationship_info_tags( + client: InfrahubClient, + mock_schema_query_01: HTTPXMock, + data: dict | list, + is_valid: bool, + format: RelationshipDataFormat, +): + location_schema = await client.schema.get(kind="BuiltinLocation") + + rel_info = await get_relationship_info(client, location_schema, "tags", data) + assert rel_info.is_valid == is_valid + assert rel_info.format == format diff --git a/tests/unit/sdk/test_client.py b/tests/unit/sdk/test_client.py index f7bad76f..4f7f3ac3 100644 --- a/tests/unit/sdk/test_client.py +++ b/tests/unit/sdk/test_client.py @@ -7,6 +7,8 @@ from infrahub_sdk.exceptions import NodeNotFoundError from infrahub_sdk.node import InfrahubNode, InfrahubNodeSync +pytestmark = pytest.mark.httpx_mock(can_send_already_matched_responses=True) + excluded_methods = ["request_context"] async_client_methods = [ @@ -243,6 +245,7 @@ async def test_method_get_by_id(httpx_mock: HTTPXMock, clients, mock_schema_quer method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -291,6 +294,7 @@ async def test_method_get_by_hfid(httpx_mock: HTTPXMock, clients, mock_schema_qu method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -338,6 +342,7 @@ async def test_method_get_by_default_filter(httpx_mock: HTTPXMock, clients, mock method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -382,6 +387,7 @@ async def test_method_get_by_name(httpx_mock: HTTPXMock, clients, mock_schema_qu method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -524,6 +530,7 @@ async def test_allocate_next_ip_address( } }, match_headers={"X-Infrahub-Tracker": "allocate-ip-loopback"}, + is_reusable=True, ) httpx_mock.add_response( method="POST", @@ -545,6 +552,7 @@ async def test_allocate_next_ip_address( } }, match_headers={"X-Infrahub-Tracker": "query-ipamipaddress-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -623,6 +631,7 @@ async def test_allocate_next_ip_prefix( } }, match_headers={"X-Infrahub-Tracker": "allocate-ip-interco"}, + is_reusable=True, ) httpx_mock.add_response( method="POST", @@ -644,6 +653,7 @@ async def test_allocate_next_ip_prefix( } }, match_headers={"X-Infrahub-Tracker": "query-ipamipprefix-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -723,6 +733,7 @@ async def test_query_echo(httpx_mock: HTTPXMock, echo_clients, client_type): httpx_mock.add_response( method="POST", json={"data": {"BuiltinTag": {"edges": []}}}, + is_reusable=True, ) query = """ diff --git a/tests/unit/sdk/test_data/multiple_files_valid.yml b/tests/unit/sdk/test_data/multiple_files_valid.yml new file mode 100644 index 00000000..81a35b8d --- /dev/null +++ b/tests/unit/sdk/test_data/multiple_files_valid.yml @@ -0,0 +1,10 @@ +--- +version: "1.0" +nodes: + - name: Node + namespace: Testing +--- +version: "1.0" +nodes: + - name: Node2 + namespace: Testing diff --git a/tests/unit/sdk/test_data/multiple_files_valid_not_valid.yml b/tests/unit/sdk/test_data/multiple_files_valid_not_valid.yml new file mode 100644 index 00000000..3921ad50 --- /dev/null +++ b/tests/unit/sdk/test_data/multiple_files_valid_not_valid.yml @@ -0,0 +1,11 @@ +--- +version: "1.0" +nodes: + - name: Node + namespace: Testing +--- +version: "1.0" +nodes: + - name: Node2 + namespace: Testing + - name: Node3 diff --git a/tests/unit/sdk/test_data/single_file.yml b/tests/unit/sdk/test_data/single_file.yml new file mode 100644 index 00000000..f0c22c16 --- /dev/null +++ b/tests/unit/sdk/test_data/single_file.yml @@ -0,0 +1,5 @@ +--- +version: "1.0" +nodes: + - name: Node + namespace: Testing diff --git a/tests/unit/sdk/test_protocols_generator.py b/tests/unit/sdk/test_protocols_generator.py new file mode 100644 index 00000000..9eae34b6 --- /dev/null +++ b/tests/unit/sdk/test_protocols_generator.py @@ -0,0 +1,75 @@ +from dataclasses import dataclass + +import pytest + +from infrahub_sdk import InfrahubClient +from infrahub_sdk.protocols_generator.generator import CodeGenerator + + +@dataclass +class SyncifyTestCase: + name: str + sync: bool + input: list[str] + output: list[str] + + +SYNCIFY_TEST_CASES = [ + SyncifyTestCase(name="sync-str", sync=True, input=["CoreNode"], output=["CoreNodeSync"]), + SyncifyTestCase( + name="sync-list", + sync=True, + input=["LineageSource", "CoreNode", "CoreObjectTemplate"], + output=["LineageSource", "CoreObjectTemplateSync", "CoreNodeSync"], + ), + SyncifyTestCase(name="async-str", sync=False, input=["CoreNode"], output=["CoreNode"]), + SyncifyTestCase( + name="async-list", + sync=False, + input=["LineageSource", "CoreNode", "CoreObjectTemplate"], + output=["LineageSource", "CoreObjectTemplate", "CoreNode"], + ), +] + + +@pytest.mark.parametrize( + "test_case", + [pytest.param(tc, id=tc.name) for tc in SYNCIFY_TEST_CASES], +) +async def test_filter_syncify(test_case: SyncifyTestCase): + assert CodeGenerator._jinja2_filter_syncify(value=test_case.input, sync=test_case.sync) == test_case.output + assert CodeGenerator._jinja2_filter_syncify(value=test_case.input, sync=test_case.sync) == test_case.output + + +async def test_generator(client: InfrahubClient, mock_schema_query_05): + schemas = await client.schema.fetch(branch="main") + + code_generator = CodeGenerator(schema=schemas) + sync_protocols = code_generator.render() + + assert "class LocationGeneric(CoreNodeSync)" in sync_protocols + assert "class LocationCountry(LocationGeneric)" in sync_protocols + assert "class TemplateInfraDevice(LineageSource, CoreObjectTemplateSync, CoreNodeSync)" in sync_protocols + + location_site_sync = """ +class LocationSite(LocationGeneric): + description: StringOptional + facility_id: StringOptional + name: String + physical_address: StringOptional + shortname: String + children: RelationshipManagerSync + member_of_groups: RelationshipManagerSync + parent: RelatedNodeSync + profiles: RelationshipManagerSync + servers: RelationshipManagerSync + subscriber_of_groups: RelationshipManagerSync + tags: RelationshipManagerSync +""" + + assert location_site_sync in sync_protocols + + async_protocols = code_generator.render(sync=False) + assert "class LocationGeneric(CoreNode)" in async_protocols + assert "class LocationCountry(LocationGeneric)" in async_protocols + assert "class TemplateInfraDevice(LineageSource, CoreObjectTemplate, CoreNode)" in async_protocols diff --git a/tests/unit/sdk/test_schema.py b/tests/unit/sdk/test_schema.py index ee07a137..32123226 100644 --- a/tests/unit/sdk/test_schema.py +++ b/tests/unit/sdk/test_schema.py @@ -9,11 +9,7 @@ from infrahub_sdk import Config, InfrahubClient, InfrahubClientSync from infrahub_sdk.ctl.schema import display_schema_load_errors from infrahub_sdk.exceptions import SchemaNotFoundError, ValidationError -from infrahub_sdk.schema import ( - InfrahubSchema, - InfrahubSchemaSync, - NodeSchemaAPI, -) +from infrahub_sdk.schema import BranchSchema, InfrahubSchema, InfrahubSchemaSync, NodeSchemaAPI from infrahub_sdk.schema.repository import ( InfrahubCheckDefinitionConfig, InfrahubJinja2TransformConfig, @@ -222,6 +218,34 @@ async def test_schema_wait_happy_path(clients: BothClients, client_type: list[st assert len(httpx_mock.get_requests()) == 2 +@pytest.mark.parametrize("client_type", client_types) +async def test_schema_set_cache_dict(clients: BothClients, client_type: list[str], schema_query_01_data: dict) -> None: + if client_type == "standard": + client = clients.standard + else: + client = clients.sync + + client.schema.set_cache(schema_query_01_data, branch="branch1") + assert "branch1" in client.schema.cache + assert client.schema.cache["branch1"].nodes["CoreGraphQLQuery"] + + +@pytest.mark.parametrize("client_type", client_types) +async def test_schema_set_cache_branch_schema( + clients: BothClients, client_type: list[str], schema_query_01_data: dict +) -> None: + if client_type == "standard": + client = clients.standard + else: + client = clients.sync + + schema = BranchSchema.from_api_response(schema_query_01_data) + + client.schema.set_cache(schema) + assert "main" in client.schema.cache + assert client.schema.cache["main"].nodes["CoreGraphQLQuery"] + + async def test_infrahub_repository_config_getters(): repo_config = InfrahubRepositoryConfig( jinja2_transforms=[ diff --git a/tests/unit/sdk/test_yaml.py b/tests/unit/sdk/test_yaml.py index 8c2b9654..f596a088 100644 --- a/tests/unit/sdk/test_yaml.py +++ b/tests/unit/sdk/test_yaml.py @@ -19,3 +19,23 @@ def test_read_incorrect_encoding() -> None: yaml_file.load_content() assert not yaml_file.valid assert yaml_file.error_message == f"Unable to read {file} with utf-8 encoding" + + +def test_read_multiple_files() -> None: + file = here / "test_data/multiple_files_valid.yml" + yaml_files = YamlFile.load_file_from_disk(path=file) + assert len(yaml_files) == 2 + assert yaml_files[0].document_position == 1 + assert yaml_files[0].valid is True + assert yaml_files[1].document_position == 2 + assert yaml_files[1].valid is True + + +def test_read_multiple_files_invalid() -> None: + file = here / "test_data/multiple_files_valid_not_valid.yml" + yaml_files = YamlFile.load_file_from_disk(path=file) + assert len(yaml_files) == 2 + assert yaml_files[0].document_position == 1 + assert yaml_files[0].valid is True + assert yaml_files[1].document_position == 2 + assert yaml_files[1].valid is False