-
Notifications
You must be signed in to change notification settings - Fork 262
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create example XYZ validation plugin
- Loading branch information
1 parent
09e110e
commit 5eb95af
Showing
10 changed files
with
342 additions
and
0 deletions.
There are no files selected for viewing
44 changes: 44 additions & 0 deletions
44
arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
""" | ||
See COPYRIGHT.md for copyright information. | ||
""" | ||
from __future__ import annotations | ||
|
||
from typing import Any | ||
|
||
from arelle.ModelDocument import LoadingException, ModelDocument | ||
from arelle.ModelXbrl import ModelXbrl | ||
from arelle.typing import TypeGetText | ||
from arelle.utils.validate.ValidationPlugin import ValidationPlugin | ||
|
||
_: TypeGetText | ||
|
||
|
||
class ValidationPluginExtension(ValidationPlugin): | ||
def modelDocumentPullLoader( | ||
self, | ||
modelXbrl: ModelXbrl, | ||
normalizedUri: str, | ||
filepath: str, | ||
isEntry: bool, | ||
namespace: str | None, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> ModelDocument | LoadingException | None: | ||
if self.disclosureSystemFromPluginSelected(modelXbrl): | ||
return LoadingException(_("XYZ validation plugin is a template for new validation plugins and shouldn't be used directly.")) | ||
return None | ||
|
||
def modelXbrlLoadComplete( | ||
self, | ||
modelXbrl: ModelXbrl, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> None: | ||
if self.disclosureSystemFromPluginSelected(modelXbrl): | ||
if modelXbrl.modelDocument is None: | ||
modelXbrl.error( | ||
codes="XYZ.01.01", | ||
msg=_("An XBRL Report Package is required but could not be loaded"), | ||
modelObject=modelXbrl, | ||
) | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
""" | ||
See COPYRIGHT.md for copyright information. | ||
Filer Manual Guidelines: https://www.example.com/fake-xyz-filer-manual-v0.0.1.pdf | ||
""" | ||
|
||
from __future__ import annotations | ||
|
||
from pathlib import Path | ||
from typing import Any | ||
|
||
from arelle.ModelDocument import LoadingException, ModelDocument | ||
from arelle.Version import authorLabel, copyrightLabel | ||
from . import rules | ||
from .ValidationPluginExtension import ValidationPluginExtension | ||
|
||
DISCLOSURE_SYSTEM_VALIDATION_TYPE = "XYZ" | ||
|
||
DISCLOSURE_SYSTEM_2022 = "XYZ 2022" | ||
DISCLOSURE_SYSTEM_2023 = "XYZ 2023" | ||
|
||
|
||
validationPlugin = ValidationPluginExtension( | ||
disclosureSystemConfigUrl=Path(__file__).parent / "resources" / "config.xml", | ||
validationTypes=[DISCLOSURE_SYSTEM_VALIDATION_TYPE], | ||
validationRulesModule=rules, | ||
) | ||
|
||
|
||
def disclosureSystemTypes(*args: Any, **kwargs: Any) -> tuple[tuple[str, str], ...]: | ||
return validationPlugin.disclosureSystemTypes | ||
|
||
|
||
def disclosureSystemConfigURL(*args: Any, **kwargs: Any) -> str: | ||
return validationPlugin.disclosureSystemConfigURL | ||
|
||
|
||
def modelDocumentPullLoader(*args: Any, **kwargs: Any) -> ModelDocument | LoadingException | None: | ||
return validationPlugin.modelDocumentPullLoader(*args, **kwargs) | ||
|
||
|
||
def modelXbrlLoadComplete(*args: Any, **kwargs: Any) -> None: | ||
return validationPlugin.modelXbrlLoadComplete(*args, **kwargs) | ||
|
||
|
||
def validateXbrlFinally(*args: Any, **kwargs: Any) -> None: | ||
return validationPlugin.validateXbrlFinally(*args, **kwargs) | ||
|
||
|
||
def validateXbrlDtsDocument(*args: Any, **kwargs: Any) -> None: | ||
return validationPlugin.validateXbrlDtsDocument(*args, **kwargs) | ||
|
||
|
||
def validateFinally(*args: Any, **kwargs: Any) -> None: | ||
return validationPlugin.validateFinally(*args, **kwargs) | ||
|
||
|
||
__pluginInfo__ = { | ||
"name": "Validate XYZ", | ||
"version": "0.0.1", | ||
"description": "Example validation plugin for the fictitious XYZ taxonomy.", | ||
"license": "Apache-2", | ||
"author": authorLabel, | ||
"copyright": copyrightLabel, | ||
"DisclosureSystem.Types": disclosureSystemTypes, | ||
"DisclosureSystem.ConfigURL": disclosureSystemConfigURL, | ||
"ModelDocument.PullLoader": modelDocumentPullLoader, | ||
"ModelXbrl.LoadComplete": modelXbrlLoadComplete, | ||
"Validate.XBRL.Finally": validateXbrlFinally, | ||
"Validate.XBRL.DTS.document": validateXbrlDtsDocument, | ||
"Validate.Finally": validateFinally, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<DisclosureSystems | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="../../../../config/disclosuresystems.xsd"> | ||
<!-- see arelle/config/disclosuresystems.xml for full comments --> | ||
<DisclosureSystem | ||
names="XYZ 2023|XYZ-2023|xyz-2023|XYZ|xyz" | ||
description="Checks for example XYZ validation plugin for year 2023" | ||
validationType="XYZ" | ||
/> | ||
<DisclosureSystem | ||
names="XYZ 2022|XYZ-2022|xyz-2022" | ||
description="Checks for example XYZ validation plugin for year 2022" | ||
validationType="XYZ" | ||
/> | ||
</DisclosureSystems> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
""" | ||
See COPYRIGHT.md for copyright information. | ||
""" | ||
from __future__ import annotations | ||
|
||
from typing import Any, Iterable, cast | ||
|
||
from arelle.ValidateXbrl import ValidateXbrl | ||
from arelle.XmlValidate import VALID | ||
from arelle.typing import TypeGetText | ||
from arelle.utils.PluginHooks import ValidationHook | ||
from arelle.utils.validate.Decorator import validation | ||
from arelle.utils.validate.Validation import Validation | ||
from .. import DISCLOSURE_SYSTEM_2022, DISCLOSURE_SYSTEM_2023 | ||
|
||
_: TypeGetText | ||
|
||
POSITIVE_FACTS_PLUGIN_CACHE_KEY = "positiveFacts" | ||
|
||
|
||
# rule 01.01 (2022) | ||
@validation( | ||
hook=ValidationHook.XBRL_FINALLY, | ||
disclosureSystems=DISCLOSURE_SYSTEM_2022, | ||
) | ||
def rule01_01_2022( | ||
pluginCache: dict[str, Any], | ||
val: ValidateXbrl, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> Iterable[Validation] | None: | ||
if "Cash" not in val.modelXbrl.factsByLocalName: | ||
yield Validation.error( | ||
codes="XYZ.01.01", | ||
msg=_("Cash must be reported."), | ||
modelObject=val.modelXbrl, | ||
) | ||
|
||
|
||
# rule 01.01 (2023) | ||
@validation( | ||
hook=ValidationHook.XBRL_FINALLY, | ||
disclosureSystems=DISCLOSURE_SYSTEM_2023, | ||
) | ||
def rule01_01_2023( | ||
pluginCache: dict[str, Any], | ||
val: ValidateXbrl, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> Iterable[Validation] | None: | ||
conceptLocalNamesWithPositiveFactValues = pretendExpensiveOperation(pluginCache, val) | ||
if "Cash" not in conceptLocalNamesWithPositiveFactValues: | ||
yield Validation.warning( | ||
codes="XYZ.01.01", | ||
msg=_("Cash should be reported."), | ||
modelObject=val.modelXbrl, | ||
) | ||
|
||
|
||
@validation(hook=ValidationHook.FINALLY) | ||
def rule01_02( | ||
pluginCache: dict[str, Any], | ||
val: ValidateXbrl, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> Iterable[Validation] | None: | ||
numXbrlErrors = len(val.modelXbrl.errors) | ||
if numXbrlErrors > 0: | ||
yield Validation.error( | ||
codes="XYZ.01.02", | ||
msg=_("Invalid report %(numXbrlErrors)s detected."), | ||
modelObject=val.modelXbrl, | ||
numXbrlErrors=numXbrlErrors, | ||
) | ||
|
||
|
||
@validation( | ||
hook=ValidationHook.XBRL_FINALLY, | ||
disclosureSystems=DISCLOSURE_SYSTEM_2023, | ||
) | ||
def rule01_03( | ||
pluginCache: dict[str, Any], | ||
val: ValidateXbrl, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> Iterable[Validation] | None: | ||
conceptLocalNamesWithPositiveFactValues = pretendExpensiveOperation(pluginCache, val) | ||
if "UnitsSold" not in conceptLocalNamesWithPositiveFactValues: | ||
yield Validation.error( | ||
codes="XYZ.01.03", | ||
msg=_("UnitsSold must be reported."), | ||
modelObject=val.modelXbrl, | ||
) | ||
|
||
|
||
def pretendExpensiveOperation( | ||
pluginCache: dict[str, Any], val: ValidateXbrl | ||
) -> set[str]: | ||
positiveFactConcepts: set[str] | None = pluginCache.get( | ||
POSITIVE_FACTS_PLUGIN_CACHE_KEY | ||
) | ||
if positiveFactConcepts is None: | ||
positiveFactConcepts = { | ||
fact.localName | ||
for fact in val.modelXbrl.facts | ||
if fact.isNumeric | ||
and getattr(fact, "xValid", 0) >= VALID | ||
and cast(int, fact.xValue) > 0 | ||
} | ||
pluginCache[POSITIVE_FACTS_PLUGIN_CACHE_KEY] = positiveFactConcepts | ||
return positiveFactConcepts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
""" | ||
See COPYRIGHT.md for copyright information. | ||
""" | ||
from __future__ import annotations | ||
|
||
from typing import Any, Iterable | ||
|
||
from arelle import XbrlConst | ||
from arelle.ModelDocument import ModelDocument, Type as ModelDocumentType | ||
from arelle.ValidateXbrl import ValidateXbrl | ||
from arelle.typing import TypeGetText | ||
from arelle.utils.PluginHooks import ValidationHook | ||
from arelle.utils.validate.Decorator import validation | ||
from arelle.utils.validate.Validation import Validation | ||
from .. import DISCLOSURE_SYSTEM_2022 | ||
|
||
_: TypeGetText | ||
|
||
|
||
@validation(hook=ValidationHook.XBRL_DTS_DOCUMENT) | ||
def rule02_01( | ||
pluginCache: dict[str, Any], | ||
val: ValidateXbrl, | ||
modelDocument: ModelDocument, | ||
isFilingDocument: bool, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> Iterable[Validation] | None: | ||
if ( | ||
modelDocument.type == ModelDocumentType.SCHEMA | ||
and modelDocument.targetNamespace is not None | ||
and len(modelDocument.targetNamespace) > 100 | ||
): | ||
yield Validation.error( | ||
codes="XYZ.02.01", | ||
msg=_("TargetNamespace is too long %(namespace)s."), | ||
modelObject=val.modelXbrl, | ||
namespace=modelDocument.targetNamespace, | ||
) | ||
|
||
|
||
@validation( | ||
hook=ValidationHook.XBRL_FINALLY, | ||
excludeDisclosureSystems=DISCLOSURE_SYSTEM_2022, | ||
) | ||
def rule02_02( | ||
pluginCache: dict[str, Any], | ||
val: ValidateXbrl, | ||
*args: Any, | ||
**kwargs: Any, | ||
) -> Iterable[Validation] | None: | ||
if val.modelXbrl.relationshipSet(XbrlConst.summationItem): | ||
yield Validation.error( | ||
codes="XYZ.02.02", | ||
msg=_("XBRL 2.1 calculations detected. XYZ 2023 taxonomy requires calc 1.1."), | ||
modelObject=val.modelXbrl, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,5 +6,6 @@ | |
:::{toctree} | ||
getting_started | ||
publishing | ||
validation | ||
hooks | ||
::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Create a Validation Plugin | ||
|
||
:::{index} Create a Validation Plugin | ||
::: | ||
|
||
One of the more common reasons to build a plugin is to add support for taxonomy or jurisdiction specific validation rules. | ||
To help accelerate the process of creating new validation plugins there's a [ValidationPlugin][validation-plugin] class | ||
and [@validation][validation-decorator] decorator for writing validation rule functions along with an [example template validation plugin][example-plugin] | ||
that demonstrates how to use them together and can be copied as a starting point. | ||
|
||
## Steps to Create a New Validation Plugin | ||
|
||
1. Copy the [XYZ validation plugin][example-plugin] from the examples directory into the [Arelle/plugins/validate directory][validations-directory]. | ||
2. Rename the plugin module from `XYZ` to the name of the taxonomy or jurisdiction you're implementing validation rules for. | ||
3. Update the resources/config.xml disclosure system file with details for your plugin. | ||
4. Update the `__init__.py` module: | ||
1. If there's a filer manual or other documentation for the rules you're implementing available online, | ||
update the comment at the top of the `__init__.py` module with a link. | ||
2. Update the `__pluginInfo__` details with the name and description of your plugin. | ||
3. Update the `DISCLOSURE_SYSTEM_VALIDATION_TYPE` variable to match the validation type you used in resources/config.xml. | ||
4. Remove any of the plugin hooks you don't need, including the functions defined in the `ValidationPluginExtension` class. | ||
5. Implement the plugin specific validation rules in the rules directory using functions and the [@validation][validation-decorator] decorator. | ||
6. [Open a PR][contributing-code] to have your plugin merged into Arelle. | ||
|
||
## Example of a validation rule | ||
|
||
:::{literalinclude} ../../../../arelle/examples/plugin/validate/XYZ/rules/rules01.py | ||
:start-after: "# rule 01.01 (2022)" | ||
:end-before: "# rule 01.01 (2023)" | ||
::: | ||
|
||
[validation-plugin]: #arelle.utils.validate.ValidationPlugin.ValidationPlugin | ||
[validation-decorator]: #arelle.utils.validate.Decorator.validation | ||
[example-plugin]: https://github.com/Arelle/Arelle/tree/master/arelle/examples/plugin/validate/XYZ | ||
[validations-directory]: https://github.com/Arelle/Arelle/tree/master/arelle/plugin/validate | ||
[contributing-code]: project:../../contributing.md#contributing-code |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters