arelle-release 2.37.45__py3-none-any.whl → 2.37.47__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arelle-release might be problematic. Click here for more details.

arelle/CntlrCmdLine.py CHANGED
@@ -1012,6 +1012,11 @@ class CntlrCmdLine(Cntlr.Cntlr):
1012
1012
 
1013
1013
  for pluginXbrlMethod in PluginManager.pluginClassMethods("CntlrCmdLine.Filing.Start"):
1014
1014
  pluginXbrlMethod(self, options, filesource, _entrypointFiles, sourceZipStream=sourceZipStream, responseZipStream=responseZipStream)
1015
+
1016
+ if options.validate:
1017
+ for pluginXbrlMethod in PluginManager.pluginClassMethods("Validate.FileSource"):
1018
+ pluginXbrlMethod(self, filesource, _entrypointFiles)
1019
+
1015
1020
  if len(_entrypointFiles) == 0 and not options.packages:
1016
1021
  if options.entrypointFile:
1017
1022
  msg = _("No XBRL entry points could be loaded from provided file: {}").format(options.entrypointFile)
arelle/ErrorManager.py CHANGED
@@ -13,7 +13,7 @@ from arelle import UrlUtil, XmlUtil, ModelValue, XbrlConst
13
13
  from arelle.FileSource import FileSource
14
14
  from arelle.Locale import format_string
15
15
  from arelle.ModelObject import ModelObject, ObjectPropertyViewWrapper
16
- from arelle.PluginManager import pluginClassMethods
16
+ from arelle.PluginManager import hasPluginWithHook, pluginClassMethods
17
17
  from arelle.PythonUtil import flattenSequence
18
18
 
19
19
  if TYPE_CHECKING:
@@ -26,10 +26,10 @@ EMPTY_TUPLE: EmptyTuple = ()
26
26
 
27
27
 
28
28
  class ErrorManager:
29
+ logHasRelevelerPlugin: bool | None
29
30
  _errorCaptureLevel: int
30
31
  _errors: list[str | None]
31
32
  _logCount: dict[str, int] = {}
32
- _logHasRelevelerPlugin: bool
33
33
  _logRefFileRelUris: defaultdict[Any, dict[str, str]]
34
34
  _modelManager: ModelManager
35
35
 
@@ -39,6 +39,7 @@ class ErrorManager:
39
39
  self._logCount = {}
40
40
  self._logRefFileRelUris = defaultdict(dict)
41
41
  self._modelManager = modelManager
42
+ self.logHasRelevelerPlugin = None
42
43
 
43
44
  @property
44
45
  def errors(self) -> list[str | None]:
@@ -111,7 +112,9 @@ class ErrorManager:
111
112
  if messageCode == "asrtNoLog":
112
113
  self._errors.append(args["assertionResults"])
113
114
  return
114
- if sourceModelXbrl is not None and any(True for m in pluginClassMethods("Logging.Severity.Releveler")):
115
+ if self.logHasRelevelerPlugin is None:
116
+ self.logHasRelevelerPlugin = hasPluginWithHook("Logging.Severity.Releveler")
117
+ if sourceModelXbrl is not None and self.logHasRelevelerPlugin:
115
118
  for pluginXbrlMethod in pluginClassMethods("Logging.Severity.Releveler"):
116
119
  level, messageCode = pluginXbrlMethod(sourceModelXbrl, level, messageCode, args) # args must be passed as dict because it may contain modelXbrl or messageCode key value
117
120
  if (messageCode and
@@ -157,10 +160,7 @@ class ErrorManager:
157
160
  fmtArgs: dict[str, LoggableValue] = {}
158
161
  extras: dict[str, Any] = {"messageCode":messageCode}
159
162
  modelObjectArgs: tuple[Any, ...] | list[Any] = ()
160
- sourceModelDocument = None
161
- if sourceModelXbrl is not None:
162
- sourceModelDocument = sourceModelXbrl.modelDocument
163
-
163
+ sourceModelDocument = getattr(sourceModelXbrl, "modelDocument", None)
164
164
  for argName, argValue in codedArgs.items():
165
165
  if argName in ("modelObject", "modelXbrl", "modelDocument"):
166
166
  if sourceModelDocument is not None:
@@ -204,13 +204,22 @@ class ErrorManager:
204
204
  _arg:ModelObject = arg.modelObject if isinstance(arg, ObjectPropertyViewWrapper) else arg
205
205
  if len(modelObjectArgs) > 1 and getattr(arg,"tag",None) == "instance":
206
206
  continue # skip IXDS top level element
207
- fragmentIdentifier = cast(str, XmlUtil.elementFragmentIdentifier(_arg))
208
- if not hasattr(_arg, 'modelDocument') and _arg.namespaceURI == XbrlConst.svg:
207
+ fragmentIdentifier = "#" + cast(str, XmlUtil.elementFragmentIdentifier(_arg))
208
+ if not hasattr(_arg, 'modelDocument') and _arg.namespaceURI == XbrlConst.svg and len(refs) > 0:
209
209
  # This is an embedded SVG document without its own file.
210
- ref["href"] = "#" + fragmentIdentifier
210
+ # Set the href to the containing document element that defined the encoded SVG.
211
+ # and define a nestedHrefs attribute with the fragment identifier.
212
+ priorRef = refs[-1]
213
+ ref["href"] = priorRef["href"]
214
+ priorNestedHrefs = priorRef.get("customAttributes", {}).get("nestedHrefs", [])
215
+ ref["customAttributes"] = {
216
+ "nestedHrefs": [*priorNestedHrefs, fragmentIdentifier]
217
+ }
218
+ if priorArgSourceline := priorRef.get("sourceLine"):
219
+ ref["sourceLine"] = priorArgSourceline
211
220
  else:
212
- ref["href"] = file + "#" + fragmentIdentifier
213
- ref["sourceLine"] = _arg.sourceline
221
+ ref["href"] = file + fragmentIdentifier
222
+ ref["sourceLine"] = _arg.sourceline
214
223
  ref["objectId"] = _arg.objectId()
215
224
  if logRefObjectProperties:
216
225
  try:
arelle/PluginManager.py CHANGED
@@ -602,6 +602,10 @@ def loadModule(moduleInfo: dict[str, Any], packagePrefix: str="") -> None:
602
602
  logPluginTrace(_msg, logging.ERROR)
603
603
 
604
604
 
605
+ def hasPluginWithHook(name: str) -> bool:
606
+ return next(pluginClassMethods(name), None) is not None
607
+
608
+
605
609
  def pluginClassMethods(className: str) -> Iterator[Callable[..., Any]]:
606
610
  if pluginConfig:
607
611
  try:
arelle/Validate.py CHANGED
@@ -398,6 +398,8 @@ class Validate:
398
398
  filesource.select(None) # must select loadable reports (not the taxonomy package itself)
399
399
  elif not filesource.isReportPackage:
400
400
  entrypoints = filesourceEntrypointFiles(filesource)
401
+ for pluginXbrlMethod in pluginClassMethods("Validate.FileSource"):
402
+ pluginXbrlMethod(self.modelXbrl.modelManager.cntlr, filesource, entrypoints)
401
403
  if entrypoints:
402
404
  # resolve an IXDS in entrypoints
403
405
  for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ArchiveIxds"):
@@ -421,6 +423,8 @@ class Validate:
421
423
  if not reportPackageErrors:
422
424
  assert isinstance(filesource.basefile, str)
423
425
  if entrypoints := filesourceEntrypointFiles(filesource):
426
+ for pluginXbrlMethod in pluginClassMethods("Validate.FileSource"):
427
+ pluginXbrlMethod(self.modelXbrl.modelManager.cntlr, filesource, entrypoints)
424
428
  for pluginXbrlMethod in pluginClassMethods("ModelTestcaseVariation.ArchiveIxds"):
425
429
  pluginXbrlMethod(self, filesource, entrypoints)
426
430
  for entrypoint in entrypoints:
arelle/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.37.45'
21
- __version_tuple__ = version_tuple = (2, 37, 45)
20
+ __version__ = version = '2.37.47'
21
+ __version_tuple__ = version_tuple = (2, 37, 47)
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  from typing import Any
7
7
 
8
+ from arelle.Cntlr import Cntlr
8
9
  from arelle.ModelDocument import LoadingException, ModelDocument
9
10
  from arelle.ModelXbrl import ModelXbrl
10
11
  from arelle.ValidateXbrl import ValidateXbrl
@@ -16,7 +17,7 @@ _: TypeGetText
16
17
 
17
18
 
18
19
  class ValidationPluginExtension(ValidationPlugin):
19
- def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginValidationDataExtension:
20
+ def newPluginData(self, cntlr: Cntlr, validateXbrl: ValidateXbrl | None) -> PluginValidationDataExtension:
20
21
  return PluginValidationDataExtension(self.name)
21
22
 
22
23
  def modelDocumentPullLoader(
@@ -3,6 +3,7 @@ See COPYRIGHT.md for copyright information.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
+ from arelle.Cntlr import Cntlr
6
7
  from arelle.ModelValue import qname
7
8
  from arelle.ValidateXbrl import ValidateXbrl
8
9
  from arelle.typing import TypeGetText
@@ -26,7 +27,7 @@ REQUIRED_DISCLOSURE_OF_EQUITY_FACTS = 2
26
27
 
27
28
 
28
29
  class ValidationPluginExtension(ValidationPlugin):
29
- def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginValidationDataExtension:
30
+ def newPluginData(self, cntlr: Cntlr, validateXbrl: ValidateXbrl | None) -> PluginValidationDataExtension:
30
31
  return PluginValidationDataExtension(
31
32
  self.name,
32
33
  accountingPolicyConceptQns=frozenset([
@@ -3,12 +3,20 @@ See COPYRIGHT.md for copyright information.
3
3
  """
4
4
  from __future__ import annotations
5
5
 
6
+ import zipfile
7
+ from collections import defaultdict
6
8
  from dataclasses import dataclass
9
+ from functools import lru_cache
10
+ from pathlib import Path
7
11
  from typing import TYPE_CHECKING
8
12
 
9
13
  from arelle.Cntlr import Cntlr
14
+ from arelle.FileSource import FileSource
15
+ from arelle.ModelXbrl import ModelXbrl
10
16
  from arelle.typing import TypeGetText
11
17
  from arelle.utils.PluginData import PluginData
18
+ from .InstanceType import InstanceType
19
+ from .UploadContents import UploadContents
12
20
 
13
21
  if TYPE_CHECKING:
14
22
  from .ManifestInstance import ManifestInstance
@@ -39,6 +47,82 @@ class ControllerPluginData(PluginData):
39
47
  """
40
48
  return list(self._manifestInstancesById.values())
41
49
 
50
+ @lru_cache(1)
51
+ def getUploadContents(self, fileSource: FileSource) -> UploadContents:
52
+ uploadFilepaths = self.getUploadFilepaths(fileSource)
53
+ amendmentPaths = defaultdict(list)
54
+ unknownPaths = []
55
+ directories = []
56
+ forms = defaultdict(list)
57
+ for path in uploadFilepaths:
58
+ parents = list(reversed([p.name for p in path.parents if len(p.name) > 0]))
59
+ if len(parents) == 0:
60
+ continue
61
+ if parents[0] == 'XBRL':
62
+ if len(parents) > 1:
63
+ formName = parents[1]
64
+ instanceType = InstanceType.parse(formName)
65
+ if instanceType is not None:
66
+ forms[instanceType].append(path)
67
+ continue
68
+ formName = parents[0]
69
+ instanceType = InstanceType.parse(formName)
70
+ if instanceType is not None:
71
+ amendmentPaths[instanceType].append(path)
72
+ continue
73
+ if len(path.suffix) == 0:
74
+ directories.append(path)
75
+ continue
76
+ unknownPaths.append(path)
77
+ return UploadContents(
78
+ amendmentPaths={k: frozenset(v) for k, v in amendmentPaths.items() if len(v) > 0},
79
+ directories=frozenset(directories),
80
+ instances={k: frozenset(v) for k, v in forms.items() if len(v) > 0},
81
+ unknownPaths=frozenset(unknownPaths)
82
+ )
83
+
84
+ @lru_cache(1)
85
+ def getUploadFilepaths(self, fileSource: FileSource) -> list[Path]:
86
+ if not self.isUpload(fileSource):
87
+ return []
88
+ paths = set()
89
+ assert isinstance(fileSource.fs, zipfile.ZipFile)
90
+ for name in fileSource.fs.namelist():
91
+ path = Path(name)
92
+ paths.add(path)
93
+ paths.update(path.parents)
94
+ return sorted(paths)
95
+
96
+ @lru_cache(1)
97
+ def getUploadFileSizes(self, fileSource: FileSource) -> dict[Path, int]:
98
+ """
99
+ Get the sizes of files in the upload directory.
100
+ :param fileSource: The FileSource instance to get file sizes for.
101
+ :return: A dictionary mapping file paths to their sizes.
102
+ """
103
+ if not self.isUpload(fileSource):
104
+ return {}
105
+ assert isinstance(fileSource.fs, zipfile.ZipFile)
106
+ return {
107
+ Path(i.filename): i.file_size
108
+ for i in fileSource.fs.infolist()
109
+ if not i.is_dir()
110
+ }
111
+
112
+ @lru_cache(1)
113
+ def isUpload(self, fileSource: FileSource) -> bool:
114
+ fileSource.open() # Make sure file source is open
115
+ if (fileSource.fs is None or
116
+ not isinstance(fileSource.fs, zipfile.ZipFile)):
117
+ if fileSource.cntlr is not None:
118
+ fileSource.cntlr.error(
119
+ level="WARNING",
120
+ codes="EDINET.uploadNotValidated",
121
+ msg=_("The target file is not a zip file, so upload validation could not be performed.")
122
+ )
123
+ return False
124
+ return True
125
+
42
126
  def matchManifestInstance(self, ixdsDocUrls: list[str]) -> ManifestInstance | None:
43
127
  """
44
128
  Match a manifest instance based on the provided ixdsDocUrls.
@@ -4,7 +4,6 @@ See COPYRIGHT.md for copyright information.
4
4
  from __future__ import annotations
5
5
 
6
6
  import zipfile
7
- from collections import defaultdict
8
7
  from dataclasses import dataclass
9
8
  from functools import lru_cache
10
9
  from pathlib import Path
@@ -18,26 +17,16 @@ from arelle.ModelValue import QName, qname
18
17
  from arelle.ModelXbrl import ModelXbrl
19
18
  from arelle.PrototypeDtsObject import LinkPrototype
20
19
  from arelle.ValidateDuplicateFacts import getDeduplicatedFacts, DeduplicationType
21
- from arelle.ValidateXbrl import ValidateXbrl
22
20
  from arelle.XmlValidate import VALID
23
21
  from arelle.typing import TypeGetText
24
22
  from arelle.utils.PluginData import PluginData
25
23
  from .Constants import CORPORATE_FORMS
26
24
  from .ControllerPluginData import ControllerPluginData
27
- from .InstanceType import InstanceType
28
25
  from .ManifestInstance import ManifestInstance
29
26
 
30
27
  _: TypeGetText
31
28
 
32
29
 
33
- @dataclass(frozen=True)
34
- class UploadContents:
35
- amendmentPaths: dict[InstanceType, frozenset[Path]]
36
- directories: frozenset[Path]
37
- instances: dict[InstanceType, frozenset[Path]]
38
- unknownPaths: frozenset[Path]
39
-
40
-
41
30
  @dataclass
42
31
  class PluginValidationDataExtension(PluginData):
43
32
  assetsIfrsQn: QName
@@ -83,32 +72,6 @@ class PluginValidationDataExtension(PluginData):
83
72
  return True
84
73
  return False
85
74
 
86
- @lru_cache(1)
87
- def shouldValidateUpload(self, val: ValidateXbrl) -> bool:
88
- """
89
- Determine if the upload validation should be performed on this model.
90
-
91
- Upload validation should not be performed if the target document is
92
- not a zipfile.
93
-
94
- Upload validation should only be performed once for the entire package,
95
- not duplicated for each model. To facilitate this with Arelle's validation
96
- system which largely prevents referencing other models, we can use `--keepOpen`
97
- and check if the given model is the first to be loaded.
98
- :param val: The ValidateXbrl instance with a model to check.
99
- :return: True if upload validation should be performed, False otherwise.
100
- """
101
- modelXbrl = val.modelXbrl
102
- if modelXbrl == val.testModelXbrl:
103
- # Not running within a testcase
104
- if modelXbrl != modelXbrl.modelManager.loadedModelXbrls[0]:
105
- return False
106
- if not modelXbrl.fileSource.fs:
107
- return False # No stream
108
- if not isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile):
109
- return False # Not a zipfile
110
- return True
111
-
112
75
  @lru_cache(1)
113
76
  def getDeduplicatedFacts(self, modelXbrl: ModelXbrl) -> list[ModelFact]:
114
77
  return getDeduplicatedFacts(modelXbrl, DeduplicationType.CONSISTENT_PAIRS)
@@ -149,86 +112,9 @@ class PluginValidationDataExtension(PluginData):
149
112
  controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
150
113
  return controllerPluginData.matchManifestInstance(modelXbrl.ixdsDocUrls)
151
114
 
152
- @lru_cache(1)
153
- def getManifestInstances(self, modelXbrl: ModelXbrl) -> list[ManifestInstance]:
154
- controllerPluginData = ControllerPluginData.get(modelXbrl.modelManager.cntlr, self.name)
155
- return controllerPluginData.getManifestInstances()
156
-
157
- def getUploadFileSizes(self, modelXbrl: ModelXbrl) -> dict[Path, int]:
158
- """
159
- Get the sizes of files in the upload directory.
160
- :param modelXbrl: The ModelXbrl instance to get file sizes for.
161
- :return: A dictionary mapping file paths to their sizes.
162
- """
163
- if not self.isUpload(modelXbrl):
164
- return {}
165
- assert isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile)
166
- return {
167
- Path(i.filename): i.file_size
168
- for i in modelXbrl.fileSource.fs.infolist()
169
- if not i.is_dir()
170
- }
171
-
172
- @lru_cache(1)
173
- def getUploadContents(self, modelXbrl: ModelXbrl) -> UploadContents:
174
- uploadFilepaths = self.getUploadFilepaths(modelXbrl)
175
- amendmentPaths = defaultdict(list)
176
- unknownPaths = []
177
- directories = []
178
- forms = defaultdict(list)
179
- for path in uploadFilepaths:
180
- parents = list(reversed([p.name for p in path.parents if len(p.name) > 0]))
181
- if len(parents) == 0:
182
- continue
183
- if parents[0] == 'XBRL':
184
- if len(parents) > 1:
185
- formName = parents[1]
186
- instanceType = InstanceType.parse(formName)
187
- if instanceType is not None:
188
- forms[instanceType].append(path)
189
- continue
190
- formName = parents[0]
191
- instanceType = InstanceType.parse(formName)
192
- if instanceType is not None:
193
- amendmentPaths[instanceType].append(path)
194
- continue
195
- if len(path.suffix) == 0:
196
- directories.append(path)
197
- continue
198
- unknownPaths.append(path)
199
- return UploadContents(
200
- amendmentPaths={k: frozenset(v) for k, v in amendmentPaths.items() if len(v) > 0},
201
- directories=frozenset(directories),
202
- instances={k: frozenset(v) for k, v in forms.items() if len(v) > 0},
203
- unknownPaths=frozenset(unknownPaths)
204
- )
205
-
206
- @lru_cache(1)
207
- def getUploadFilepaths(self, modelXbrl: ModelXbrl) -> list[Path]:
208
- if not self.isUpload(modelXbrl):
209
- return []
210
- paths = set()
211
- assert isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile)
212
- for name in modelXbrl.fileSource.fs.namelist():
213
- path = Path(name)
214
- paths.add(path)
215
- paths.update(path.parents)
216
- return sorted(paths)
217
-
218
115
  def hasValidNonNilFact(self, modelXbrl: ModelXbrl, qname: QName) -> bool:
219
116
  requiredFacts = modelXbrl.factsByQname.get(qname, set())
220
117
  return any(fact.xValid >= VALID and not fact.isNil for fact in requiredFacts)
221
118
 
222
- @lru_cache(1)
223
- def isUpload(self, modelXbrl: ModelXbrl) -> bool:
224
- if not modelXbrl.fileSource.fs or \
225
- not isinstance(modelXbrl.fileSource.fs, zipfile.ZipFile):
226
- modelXbrl.warning(
227
- codes="EDINET.uploadNotValidated",
228
- msg=_("The target file is not a zip file, so upload validation could not be performed.")
229
- )
230
- return False
231
- return True
232
-
233
119
  def isStandardTaxonomyUrl(self, uri: str, modelXbrl: ModelXbrl) -> bool:
234
120
  return modelXbrl.modelManager.disclosureSystem.hrefValidForDisclosureSystem(uri)
@@ -0,0 +1,17 @@
1
+ """
2
+ See COPYRIGHT.md for copyright information.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ from .InstanceType import InstanceType
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class UploadContents:
14
+ amendmentPaths: dict[InstanceType, frozenset[Path]]
15
+ directories: frozenset[Path]
16
+ instances: dict[InstanceType, frozenset[Path]]
17
+ unknownPaths: frozenset[Path]
@@ -5,9 +5,11 @@ from __future__ import annotations
5
5
 
6
6
  from typing import Any
7
7
 
8
+ from arelle.Cntlr import Cntlr
8
9
  from arelle.FileSource import FileSource
9
10
  from arelle.ValidateXbrl import ValidateXbrl
10
11
  from arelle.typing import TypeGetText
12
+ from arelle.utils.PluginData import PluginData
11
13
  from arelle.utils.validate.ValidationPlugin import ValidationPlugin
12
14
  from .ControllerPluginData import ControllerPluginData
13
15
  from .DisclosureSystems import DISCLOSURE_SYSTEM_EDINET
@@ -35,8 +37,12 @@ class ValidationPluginExtension(ValidationPlugin):
35
37
  entrypointFiles.append({'ixds': entrypoints, 'id': instance.id})
36
38
  return entrypointFiles
37
39
 
38
- def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginValidationDataExtension:
39
- disclosureSystem = validateXbrl.disclosureSystem.name
40
+ def newPluginData(self, cntlr: Cntlr, validateXbrl: ValidateXbrl | None) -> PluginData:
41
+ if validateXbrl is None:
42
+ return ControllerPluginData.get(cntlr, self.name)
43
+ disclosureSystem = DISCLOSURE_SYSTEM_EDINET
44
+ if validateXbrl is not None:
45
+ disclosureSystem = str(validateXbrl.disclosureSystem.name)
40
46
  if disclosureSystem == DISCLOSURE_SYSTEM_EDINET:
41
47
  pass
42
48
  else:
@@ -59,6 +59,10 @@ def loggingSeverityReleveler(modelXbrl: ModelXbrl, level: str, messageCode: str,
59
59
  return level, messageCode
60
60
 
61
61
 
62
+ def validateFileSource(*args: Any, **kwargs: Any) -> None:
63
+ return validationPlugin.validateFileSource(*args, **kwargs)
64
+
65
+
62
66
  def validateFinally(*args: Any, **kwargs: Any) -> None:
63
67
  return validationPlugin.validateFinally(*args, **kwargs)
64
68
 
@@ -79,6 +83,7 @@ __pluginInfo__ = {
79
83
  "DisclosureSystem.ConfigURL": disclosureSystemConfigURL,
80
84
  "FileSource.EntrypointFiles": fileSourceEntrypointFiles,
81
85
  "Logging.Severity.Releveler": loggingSeverityReleveler,
86
+ "Validate.FileSource": validateFileSource,
82
87
  "Validate.XBRL.Finally": validateXbrlFinally,
83
88
  "ValidateFormula.Finished": validateFinally,
84
89
  }
@@ -6,8 +6,10 @@ from __future__ import annotations
6
6
  import re
7
7
  from collections import defaultdict
8
8
  from pathlib import Path
9
- from typing import Any, Iterable
9
+ from typing import Any, Iterable, TYPE_CHECKING
10
10
 
11
+ from arelle.Cntlr import Cntlr
12
+ from arelle.FileSource import FileSource
11
13
  from arelle.ValidateXbrl import ValidateXbrl
12
14
  from arelle.typing import TypeGetText
13
15
  from arelle.utils.PluginHooks import ValidationHook
@@ -17,6 +19,9 @@ from ..DisclosureSystems import (DISCLOSURE_SYSTEM_EDINET)
17
19
  from ..InstanceType import InstanceType, HTML_EXTENSIONS, IMAGE_EXTENSIONS
18
20
  from ..PluginValidationDataExtension import PluginValidationDataExtension
19
21
 
22
+ if TYPE_CHECKING:
23
+ from ..ControllerPluginData import ControllerPluginData
24
+
20
25
  _: TypeGetText
21
26
 
22
27
  FILE_COUNT_LIMITS = {
@@ -35,12 +40,13 @@ FILENAME_STEM_PATTERN = re.compile(r'[a-zA-Z0-9_-]*')
35
40
 
36
41
 
37
42
  @validation(
38
- hook=ValidationHook.XBRL_FINALLY,
43
+ hook=ValidationHook.FILESOURCE,
39
44
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
40
45
  )
41
46
  def rule_EC0121E(
42
- pluginData: PluginValidationDataExtension,
43
- val: ValidateXbrl,
47
+ pluginData: ControllerPluginData,
48
+ cntlr: Cntlr,
49
+ fileSource: FileSource,
44
50
  *args: Any,
45
51
  **kwargs: Any,
46
52
  ) -> Iterable[Validation]:
@@ -53,9 +59,7 @@ def rule_EC0121E(
53
59
  i.e. amendment documents. For now, we will only check amendment documents, directory
54
60
  names, or other files in unexpected locations.
55
61
  """
56
- if not pluginData.shouldValidateUpload(val):
57
- return
58
- uploadContents = pluginData.getUploadContents(val.modelXbrl)
62
+ uploadContents = pluginData.getUploadContents(fileSource)
59
63
  paths = set(uploadContents.directories | uploadContents.unknownPaths)
60
64
  for amendmentPaths in uploadContents.amendmentPaths.values():
61
65
  paths.update(amendmentPaths)
@@ -75,21 +79,20 @@ def rule_EC0121E(
75
79
 
76
80
 
77
81
  @validation(
78
- hook=ValidationHook.XBRL_FINALLY,
82
+ hook=ValidationHook.FILESOURCE,
79
83
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
80
84
  )
81
85
  def rule_EC0124E(
82
- pluginData: PluginValidationDataExtension,
83
- val: ValidateXbrl,
86
+ pluginData: ControllerPluginData,
87
+ cntlr: Cntlr,
88
+ fileSource: FileSource,
84
89
  *args: Any,
85
90
  **kwargs: Any,
86
91
  ) -> Iterable[Validation]:
87
92
  """
88
93
  EDINET.EC0124E: There are no empty directories.
89
94
  """
90
- if not pluginData.shouldValidateUpload(val):
91
- return
92
- uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
95
+ uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
93
96
  emptyDirectories = []
94
97
  for path in uploadFilepaths:
95
98
  if path.suffix:
@@ -107,12 +110,13 @@ def rule_EC0124E(
107
110
 
108
111
 
109
112
  @validation(
110
- hook=ValidationHook.XBRL_FINALLY,
113
+ hook=ValidationHook.FILESOURCE,
111
114
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
112
115
  )
113
116
  def rule_EC0129E(
114
- pluginData: PluginValidationDataExtension,
115
- val: ValidateXbrl,
117
+ pluginData: ControllerPluginData,
118
+ cntlr: Cntlr,
119
+ fileSource: FileSource,
116
120
  *args: Any,
117
121
  **kwargs: Any,
118
122
  ) -> Iterable[Validation]:
@@ -120,9 +124,7 @@ def rule_EC0129E(
120
124
  EDINET.EC0129E: Limit the number of subfolders to 3 or less from the XBRL directory.
121
125
  """
122
126
  startingDirectory = 'XBRL'
123
- if not pluginData.shouldValidateUpload(val):
124
- return
125
- uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
127
+ uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
126
128
  for path in uploadFilepaths:
127
129
  parents = [parent.name for parent in path.parents]
128
130
  if startingDirectory in parents:
@@ -144,21 +146,20 @@ def rule_EC0129E(
144
146
 
145
147
 
146
148
  @validation(
147
- hook=ValidationHook.XBRL_FINALLY,
149
+ hook=ValidationHook.FILESOURCE,
148
150
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
149
151
  )
150
152
  def rule_EC0130E(
151
- pluginData: PluginValidationDataExtension,
152
- val: ValidateXbrl,
153
+ pluginData: ControllerPluginData,
154
+ cntlr: Cntlr,
155
+ fileSource: FileSource,
153
156
  *args: Any,
154
157
  **kwargs: Any,
155
158
  ) -> Iterable[Validation]:
156
159
  """
157
160
  EDINET.EC0130E: File extensions must match the file extensions allowed in Figure 2-1-3 and Figure 2-1-5.
158
161
  """
159
- if not pluginData.shouldValidateUpload(val):
160
- return
161
- uploadContents = pluginData.getUploadContents(val.modelXbrl)
162
+ uploadContents = pluginData.getUploadContents(fileSource)
162
163
  checks = []
163
164
  for instanceType, amendmentPaths in uploadContents.amendmentPaths.items():
164
165
  for amendmentPath in amendmentPaths:
@@ -192,21 +193,20 @@ def rule_EC0130E(
192
193
 
193
194
 
194
195
  @validation(
195
- hook=ValidationHook.XBRL_FINALLY,
196
+ hook=ValidationHook.FILESOURCE,
196
197
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
197
198
  )
198
199
  def rule_EC0132E(
199
- pluginData: PluginValidationDataExtension,
200
- val: ValidateXbrl,
200
+ pluginData: ControllerPluginData,
201
+ cntlr: Cntlr,
202
+ fileSource: FileSource,
201
203
  *args: Any,
202
204
  **kwargs: Any,
203
205
  ) -> Iterable[Validation]:
204
206
  """
205
207
  EDINET.EC0132E: Store the manifest file directly under the relevant folder.
206
208
  """
207
- if not pluginData.shouldValidateUpload(val):
208
- return
209
- uploadContents = pluginData.getUploadContents(val.modelXbrl)
209
+ uploadContents = pluginData.getUploadContents(fileSource)
210
210
  for instanceType in (InstanceType.AUDIT_DOC, InstanceType.PRIVATE_DOC, InstanceType.PUBLIC_DOC):
211
211
  if instanceType not in uploadContents.instances:
212
212
  continue
@@ -222,21 +222,20 @@ def rule_EC0132E(
222
222
 
223
223
 
224
224
  @validation(
225
- hook=ValidationHook.XBRL_FINALLY,
225
+ hook=ValidationHook.FILESOURCE,
226
226
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
227
227
  )
228
228
  def rule_EC0183E(
229
- pluginData: PluginValidationDataExtension,
230
- val: ValidateXbrl,
229
+ pluginData: ControllerPluginData,
230
+ cntlr: Cntlr,
231
+ fileSource: FileSource,
231
232
  *args: Any,
232
233
  **kwargs: Any,
233
234
  ) -> Iterable[Validation]:
234
235
  """
235
236
  EDINET.EC0183E: The compressed file size exceeds 55MB.
236
237
  """
237
- if not pluginData.shouldValidateUpload(val):
238
- return
239
- size = val.modelXbrl.fileSource.getBytesSize()
238
+ size = fileSource.getBytesSize()
240
239
  if size is None:
241
240
  return # File size is not available, cannot validate
242
241
  if size > 55_000_000: # Interpretting MB as megabytes (1,000,000 bytes)
@@ -248,22 +247,21 @@ def rule_EC0183E(
248
247
 
249
248
 
250
249
  @validation(
251
- hook=ValidationHook.XBRL_FINALLY,
250
+ hook=ValidationHook.FILESOURCE,
252
251
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
253
252
  )
254
253
  def rule_EC0188E(
255
- pluginData: PluginValidationDataExtension,
256
- val: ValidateXbrl,
254
+ pluginData: ControllerPluginData,
255
+ cntlr: Cntlr,
256
+ fileSource: FileSource,
257
257
  *args: Any,
258
258
  **kwargs: Any,
259
259
  ) -> Iterable[Validation]:
260
260
  """
261
261
  EDINET.EC0188E: There is an HTML file directly under PublicDoc or PrivateDoc whose first 7 characters are not numbers.
262
262
  """
263
- if not pluginData.shouldValidateUpload(val):
264
- return
265
263
  pattern = re.compile(r'^\d{7}')
266
- uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
264
+ uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
267
265
  docFolders = frozenset({"PublicDoc", "PrivateDoc"})
268
266
  for path in uploadFilepaths:
269
267
  if path.suffix not in HTML_EXTENSIONS:
@@ -282,22 +280,21 @@ def rule_EC0188E(
282
280
 
283
281
 
284
282
  @validation(
285
- hook=ValidationHook.XBRL_FINALLY,
283
+ hook=ValidationHook.FILESOURCE,
286
284
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
287
285
  )
288
286
  def rule_EC0198E(
289
- pluginData: PluginValidationDataExtension,
290
- val: ValidateXbrl,
287
+ pluginData: ControllerPluginData,
288
+ cntlr: Cntlr,
289
+ fileSource: FileSource,
291
290
  *args: Any,
292
291
  **kwargs: Any,
293
292
  ) -> Iterable[Validation]:
294
293
  """
295
294
  EDINET.EC0198E: The number of files in the total submission and directories can not exceed the upper limit.
296
295
  """
297
- if not pluginData.shouldValidateUpload(val):
298
- return
299
296
  fileCounts: dict[Path, int] = defaultdict(int)
300
- uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
297
+ uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
301
298
  for path in uploadFilepaths:
302
299
  if len(path.suffix) == 0:
303
300
  continue
@@ -319,21 +316,20 @@ def rule_EC0198E(
319
316
 
320
317
 
321
318
  @validation(
322
- hook=ValidationHook.XBRL_FINALLY,
319
+ hook=ValidationHook.FILESOURCE,
323
320
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
324
321
  )
325
322
  def rule_EC0237E(
326
- pluginData: PluginValidationDataExtension,
327
- val: ValidateXbrl,
323
+ pluginData: ControllerPluginData,
324
+ cntlr: Cntlr,
325
+ fileSource: FileSource,
328
326
  *args: Any,
329
327
  **kwargs: Any,
330
328
  ) -> Iterable[Validation]:
331
329
  """
332
330
  EDINET.EC0237E: The directory or file path to the lowest level exceeds the maximum value (259 characters).
333
331
  """
334
- if not pluginData.shouldValidateUpload(val):
335
- return
336
- uploadFilepaths = pluginData.getUploadFilepaths(val.modelXbrl)
332
+ uploadFilepaths = pluginData.getUploadFilepaths(fileSource)
337
333
  for path in uploadFilepaths:
338
334
  if len(str(path)) <= 259:
339
335
  continue
@@ -348,21 +344,20 @@ def rule_EC0237E(
348
344
 
349
345
 
350
346
  @validation(
351
- hook=ValidationHook.XBRL_FINALLY,
347
+ hook=ValidationHook.FILESOURCE,
352
348
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
353
349
  )
354
350
  def rule_EC0206E(
355
- pluginData: PluginValidationDataExtension,
356
- val: ValidateXbrl,
351
+ pluginData: ControllerPluginData,
352
+ cntlr: Cntlr,
353
+ fileSource: FileSource,
357
354
  *args: Any,
358
355
  **kwargs: Any,
359
356
  ) -> Iterable[Validation]:
360
357
  """
361
358
  EDINET.EC0206E: Empty files are not permitted.
362
359
  """
363
- if not pluginData.shouldValidateUpload(val):
364
- return
365
- for path, size in pluginData.getUploadFileSizes(val.modelXbrl).items():
360
+ for path, size in pluginData.getUploadFileSizes(fileSource).items():
366
361
  if size > 0:
367
362
  continue
368
363
  yield Validation.error(
@@ -376,21 +371,20 @@ def rule_EC0206E(
376
371
 
377
372
 
378
373
  @validation(
379
- hook=ValidationHook.XBRL_FINALLY,
374
+ hook=ValidationHook.FILESOURCE,
380
375
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
381
376
  )
382
377
  def rule_EC1016E(
383
- pluginData: PluginValidationDataExtension,
384
- val: ValidateXbrl,
378
+ pluginData: ControllerPluginData,
379
+ cntlr: Cntlr,
380
+ fileSource: FileSource,
385
381
  *args: Any,
386
382
  **kwargs: Any,
387
383
  ) -> Iterable[Validation]:
388
384
  """
389
385
  EDINET.EC1016E: The image file is over 300KB.
390
386
  """
391
- if not pluginData.shouldValidateUpload(val):
392
- return
393
- for path, size in pluginData.getUploadFileSizes(val.modelXbrl).items():
387
+ for path, size in pluginData.getUploadFileSizes(fileSource).items():
394
388
  if path.suffix not in IMAGE_EXTENSIONS:
395
389
  continue
396
390
  if size <= 300_000: # Interpretting KB as kilobytes (1,000 bytes)
@@ -422,8 +416,6 @@ def rule_EC1020E(
422
416
  Note: Some violations of this rule (such as multiple DOCTYPE declarations) prevent Arelle from parsing
423
417
  the XML at all, and thus an XML schema error will be triggered rather than this validation error.
424
418
  """
425
- if not pluginData.shouldValidateUpload(val):
426
- return
427
419
  checkNames = frozenset({'body', 'head', 'html'})
428
420
  for modelDocument in val.modelXbrl.urlDocs.values():
429
421
  path = Path(modelDocument.uri)
@@ -453,12 +445,13 @@ def rule_EC1020E(
453
445
 
454
446
 
455
447
  @validation(
456
- hook=ValidationHook.XBRL_FINALLY,
448
+ hook=ValidationHook.FILESOURCE,
457
449
  disclosureSystems=[DISCLOSURE_SYSTEM_EDINET],
458
450
  )
459
451
  def rule_manifest_preferredFilename(
460
- pluginData: PluginValidationDataExtension,
461
- val: ValidateXbrl,
452
+ pluginData: ControllerPluginData,
453
+ cntlr: Cntlr,
454
+ fileSource: FileSource,
462
455
  *args: Any,
463
456
  **kwargs: Any,
464
457
  ) -> Iterable[Validation]:
@@ -474,9 +467,7 @@ def rule_manifest_preferredFilename(
474
467
  The preferredFilename attribute value of the instance element in the manifest
475
468
  file must be unique within the same file.
476
469
  """
477
- if not pluginData.shouldValidateUpload(val):
478
- return
479
- instances = pluginData.getManifestInstances(val.modelXbrl)
470
+ instances = pluginData.getManifestInstances()
480
471
  preferredFilenames: dict[Path, set[str]] = defaultdict(set)
481
472
  duplicateFilenames = defaultdict(set)
482
473
  for instance in instances:
@@ -4,6 +4,7 @@ See COPYRIGHT.md for copyright information.
4
4
  from __future__ import annotations
5
5
  from typing import Any
6
6
 
7
+ from arelle.Cntlr import Cntlr
7
8
  from arelle.ModelDocument import LoadingException, ModelDocument
8
9
  from arelle.ModelValue import qname
9
10
  from arelle.ModelXbrl import ModelXbrl
@@ -19,7 +20,8 @@ _: TypeGetText
19
20
 
20
21
 
21
22
  class ValidationPluginExtension(ValidationPlugin):
22
- def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginValidationDataExtension:
23
+ def newPluginData(self, cntlr: Cntlr, validateXbrl: ValidateXbrl | None) -> PluginValidationDataExtension:
24
+ assert validateXbrl is not None
23
25
  disclosureSystem = validateXbrl.disclosureSystem.name
24
26
  if disclosureSystem == DISCLOSURE_SYSTEM_NT16:
25
27
  ifrsNamespace = None
@@ -2,6 +2,8 @@
2
2
  See COPYRIGHT.md for copyright information.
3
3
  """
4
4
  from __future__ import annotations
5
+
6
+ from arelle.Cntlr import Cntlr
5
7
  from arelle.ValidateXbrl import ValidateXbrl
6
8
  from arelle.typing import TypeGetText
7
9
  from arelle.utils.validate.ValidationPlugin import ValidationPlugin
@@ -18,7 +20,7 @@ TURNOVER_REVENUE = 'DPLTurnoverRevenue'
18
20
 
19
21
 
20
22
  class ValidationPluginExtension(ValidationPlugin):
21
- def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginValidationDataExtension:
23
+ def newPluginData(self, cntlr: Cntlr, validateXbrl: ValidateXbrl | None) -> PluginValidationDataExtension:
22
24
  return PluginValidationDataExtension(
23
25
  self.name
24
26
  )
@@ -34,6 +34,7 @@ class ValidationHook(Enum):
34
34
  These hooks are called at different stages of validation, but all provide a common interface (ValidateXbrl is the first param).
35
35
  """
36
36
 
37
+ FILESOURCE = "Validate.FileSource"
37
38
  XBRL_START = "Validate.XBRL.Start"
38
39
  XBRL_FINALLY = "Validate.XBRL.Finally"
39
40
  XBRL_DTS_DOCUMENT = "Validate.XBRL.DTS.document"
@@ -568,6 +569,37 @@ class PluginHooks(ABC):
568
569
  """
569
570
  raise NotImplementedError
570
571
 
572
+ @staticmethod
573
+ def validateFileSource(
574
+ cntlr: Cntlr,
575
+ fileSource: FileSource,
576
+ entrypoints: list[dict[str, Any]] | None = None,
577
+ *args: Any,
578
+ **kwargs: Any,
579
+ ) -> None:
580
+ """
581
+ Plugin hook: `Validate.FileSource`
582
+
583
+ Hook for executing validation rules applicable to entrypoint files.
584
+
585
+ Example:
586
+ ```python
587
+ size = fileSource.getBytesSize()
588
+ if size is None:
589
+ return
590
+ if size > 100_000_000:
591
+ yield Validation.error(codes="0.0.0", msg="File size exceeds 100MB.")
592
+ ```
593
+
594
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
595
+ :param fileSource: The [FileSource](#arelle.FileSource.FileSource) involved in loading the entrypoint files.
596
+ :param entrypoints: A list of entrypoint configurations.
597
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
598
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
599
+ :return: None
600
+ """
601
+ raise NotImplementedError
602
+
571
603
  @staticmethod
572
604
  def validateFinally(
573
605
  val: ValidateXbrl,
@@ -8,17 +8,19 @@ from pathlib import Path
8
8
  from types import ModuleType
9
9
  from typing import Any
10
10
 
11
+ from arelle.Cntlr import Cntlr
11
12
  from arelle.DisclosureSystem import DisclosureSystem
13
+ from arelle.FileSource import FileSource
12
14
  from arelle.ModelDocument import LoadingException, ModelDocument
13
15
  from arelle.ModelXbrl import ModelXbrl
14
16
  from arelle.ValidateXbrl import ValidateXbrl
17
+ from arelle.utils.PluginData import PluginData
15
18
  from arelle.utils.PluginHooks import ValidationHook
16
19
  from arelle.utils.validate.Decorator import (
17
20
  ValidationAttributes,
18
21
  ValidationFunction,
19
22
  getValidationAttributes,
20
23
  )
21
- from arelle.utils.PluginData import PluginData
22
24
 
23
25
 
24
26
  class ValidationPlugin:
@@ -55,7 +57,7 @@ class ValidationPlugin:
55
57
  ValidationHook, dict[ValidationFunction, set[str]]
56
58
  ] = {}
57
59
 
58
- def newPluginData(self, validateXbrl: ValidateXbrl) -> PluginData:
60
+ def newPluginData(self, cntlr: Cntlr, validateXbrl: ValidateXbrl | None) -> PluginData:
59
61
  """
60
62
  Returns a dataclass intended to be overriden by plugins to facilitate caching and passing data between rule functions.
61
63
  The default implementation doesn't provide any fields other than the plugin name.
@@ -99,6 +101,28 @@ class ValidationPlugin:
99
101
  ) -> ModelDocument | LoadingException | None:
100
102
  raise NotImplementedError
101
103
 
104
+ def validateFileSource(
105
+ self,
106
+ cntlr: Cntlr,
107
+ fileSource: FileSource,
108
+ entrypoints: list[dict[str, Any]] | None = None,
109
+ *args: Any,
110
+ **kwargs: Any,
111
+ ) -> None:
112
+ """
113
+ Executes validation functions in the rules module that was provided to the constructor of this class.
114
+ Each function decorated with [@validation](#arelle.utils.validate.Decorator.validation) will be run if:
115
+ 1. the decorator was used with the xbrl start hook: `@validation(hook=ValidationHook.FILESOURCE)`
116
+
117
+ :param cntlr: The [Cntlr](#arelle.Cntlr.Cntlr) instance.
118
+ :param fileSource: The [FileSource](#arelle.FileSource.FileSource) involved in loading the entrypoint files.
119
+ :param entrypoints: A list of entrypoint configurations.
120
+ :param args: Argument capture to ensure new parameters don't break plugin hook.
121
+ :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
122
+ :return: None
123
+ """
124
+ self._executeCntlrValidations(ValidationHook.FILESOURCE, cntlr, fileSource, entrypoints, *args, **kwargs)
125
+
102
126
  def validateXbrlStart(
103
127
  self,
104
128
  val: ValidateXbrl,
@@ -118,7 +142,7 @@ class ValidationPlugin:
118
142
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
119
143
  :return: None
120
144
  """
121
- self._executeValidations(ValidationHook.XBRL_START, val, parameters, *args, **kwargs)
145
+ self._executeModelValidations(ValidationHook.XBRL_START, val, parameters, *args, **kwargs)
122
146
 
123
147
  def validateXbrlFinally(
124
148
  self,
@@ -137,7 +161,7 @@ class ValidationPlugin:
137
161
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
138
162
  :return: None
139
163
  """
140
- self._executeValidations(ValidationHook.XBRL_FINALLY, val, *args, **kwargs)
164
+ self._executeModelValidations(ValidationHook.XBRL_FINALLY, val, *args, **kwargs)
141
165
 
142
166
  def validateXbrlDtsDocument(
143
167
  self,
@@ -158,7 +182,7 @@ class ValidationPlugin:
158
182
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
159
183
  :return: None
160
184
  """
161
- self._executeValidations(ValidationHook.XBRL_DTS_DOCUMENT, val, modelDocument, isFilingDocument, *args, **kwargs)
185
+ self._executeModelValidations(ValidationHook.XBRL_DTS_DOCUMENT, val, modelDocument, isFilingDocument, *args, **kwargs)
162
186
 
163
187
  def validateFinally(
164
188
  self,
@@ -177,9 +201,28 @@ class ValidationPlugin:
177
201
  :param kwargs: Argument capture to ensure new named parameters don't break plugin hook.
178
202
  :return: None
179
203
  """
180
- self._executeValidations(ValidationHook.FINALLY, val, *args, **kwargs)
204
+ self._executeModelValidations(ValidationHook.FINALLY, val, *args, **kwargs)
205
+
206
+ def _executeCntlrValidations(
207
+ self,
208
+ pluginHook: ValidationHook,
209
+ cntlr: Cntlr,
210
+ fileSource: FileSource | None = None,
211
+ entrypoints: list[dict[str, Any]] | None = None,
212
+ *args: Any,
213
+ **kwargs: Any,
214
+ ) -> None:
215
+ pluginData = self.newPluginData(
216
+ cntlr=cntlr,
217
+ validateXbrl=None
218
+ )
219
+ for rule in self._getValidations(cntlr.modelManager.disclosureSystem, pluginHook):
220
+ validations = rule(pluginData, cntlr, fileSource, entrypoints, *args, **kwargs)
221
+ if validations is not None:
222
+ for val in validations:
223
+ cntlr.error(level=val.level.name, codes=val.codes, msg=val.msg, **val.args)
181
224
 
182
- def _executeValidations(
225
+ def _executeModelValidations(
183
226
  self,
184
227
  pluginHook: ValidationHook,
185
228
  validateXbrl: ValidateXbrl,
@@ -189,7 +232,10 @@ class ValidationPlugin:
189
232
  if self.disclosureSystemFromPluginSelected(validateXbrl):
190
233
  pluginData = validateXbrl.getPluginData(self.name)
191
234
  if pluginData is None:
192
- pluginData = self.newPluginData(validateXbrl)
235
+ pluginData = self.newPluginData(
236
+ cntlr=validateXbrl.modelXbrl.modelManager.cntlr,
237
+ validateXbrl=validateXbrl
238
+ )
193
239
  validateXbrl.setPluginData(pluginData)
194
240
  for rule in self._getValidations(validateXbrl.disclosureSystem, pluginHook):
195
241
  validations = rule(pluginData, validateXbrl, *args, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arelle-release
3
- Version: 2.37.45
3
+ Version: 2.37.47
4
4
  Summary: An open source XBRL platform.
5
5
  Author-email: "arelle.org" <support@arelle.org>
6
6
  License-Expression: Apache-2.0
@@ -1,7 +1,7 @@
1
1
  arelle/Aspect.py,sha256=Pn9I91D1os1RTVj6htuxTfRzVMhmVDtrbKvV_zy9xMI,5470
2
2
  arelle/BetaFeatures.py,sha256=T_tPac-FiozHyYLCemt0RoHJ1JahUE71L-0tHmIRKpE,858
3
3
  arelle/Cntlr.py,sha256=nwE_qIQMTa5R3sr_dLP6s3srLhiEA5zDOqfas2pDxoA,31691
4
- arelle/CntlrCmdLine.py,sha256=tSbTlREbJkppAZ9HlB_99KpDKx3EwWBV9veWnXg6hVQ,90155
4
+ arelle/CntlrCmdLine.py,sha256=BHph7DWV9m87i23btO1G7l9KFt9F1EnMsorKRY9NdG8,90348
5
5
  arelle/CntlrComServer.py,sha256=h1KPf31uMbErpxTZn_iklDqUMGFgQnjZkFkFjd8gtLQ,1888
6
6
  arelle/CntlrProfiler.py,sha256=2VQJudiUhxryVypxjODx2ccP1-n60icTiWs5lSEokhQ,972
7
7
  arelle/CntlrQuickBooks.py,sha256=BMqd5nkNQOZyNFPefkTeWUUDCYNS6BQavaG8k1Lepu4,31543
@@ -21,7 +21,7 @@ arelle/DialogRssWatch.py,sha256=mjc4pqyFDISY4tQtME0uSRQ3NlcWnNsOsMu9Zj8tTd0,1378
21
21
  arelle/DialogURL.py,sha256=JH88OPFf588E8RW90uMaieok7A_4kOAURQ8kHWVhnao,4354
22
22
  arelle/DialogUserPassword.py,sha256=kWPlCCihhwvsykDjanME9qBDtv6cxZlsrJyoMqiRep4,13769
23
23
  arelle/DisclosureSystem.py,sha256=mQlz8eezPpJuG6gHBV-x4-5Hne3LVh8TQf-Qm9jiFxI,24757
24
- arelle/ErrorManager.py,sha256=_E-kselWxG8tpCyh7J13x7U0-VUDLQIc8zo7VQajERc,15808
24
+ arelle/ErrorManager.py,sha256=en3jVyUnSDzHGFE2e8UjuitU9sBwlciRMPiSn5OpTjs,16641
25
25
  arelle/FileSource.py,sha256=asaX2wM47T7S6kELwmXm-YjGIoV6poWz_YdYThY0lpk,47983
26
26
  arelle/FunctionCustom.py,sha256=d1FsBG14eykvpLpgaXpN8IdxnlG54dfGcsXPYfdpA9Q,5880
27
27
  arelle/FunctionFn.py,sha256=BcZKah1rpfquSVPwjvknM1pgFXOnK4Hr1e_ArG_mcJY,38058
@@ -53,7 +53,7 @@ arelle/ModelVersObject.py,sha256=cPD1IzhkCfuV1eMgVFWes88DH_6WkUj5kj7sgGF2M0I,260
53
53
  arelle/ModelVersReport.py,sha256=bXEA9K3qkH57aABn5l-m3CTY0FAcF1yX6O4fo-URjl8,73326
54
54
  arelle/ModelXbrl.py,sha256=w7x74hBV-Ub4gRQ-iT4lIC13KCxp699W2FJ-AO7cebw,60639
55
55
  arelle/PackageManager.py,sha256=BvPExMcxh8rHMxogOag-PGbX6vXdhCiXAHcDLA6Ypsc,32592
56
- arelle/PluginManager.py,sha256=foSgWvRI1Ret-6KVRQMFSv4RtpEf_0UB7468N_NjPGU,42116
56
+ arelle/PluginManager.py,sha256=Gnh7xmvyIQX2PyCwjDMFZVanCM9KW09I-x5x8YfDpJs,42220
57
57
  arelle/PluginUtils.py,sha256=0vFQ29wVVpU0cTY3YOBL6FhNQhhCTwShBH4qTJGLnvc,2426
58
58
  arelle/PrototypeDtsObject.py,sha256=0lf60VcXR_isx57hBPrc7vEMkFpYkVuK4JVBSmopzkQ,7989
59
59
  arelle/PrototypeInstanceObject.py,sha256=CXUoDllhDqpMvSQjqIYi1Ywp-J8fLhQRV9wVD2YXgVo,13204
@@ -67,7 +67,7 @@ arelle/UITkTable.py,sha256=N83cXi5c0lLZLsDbwSKcPrlYoUoGsNavGN5YRx6d9XY,39810
67
67
  arelle/UiUtil.py,sha256=3G0xPclZI8xW_XQDbiFrmylB7Nd5muqi5n2x2oMkMZU,34218
68
68
  arelle/Updater.py,sha256=IZ8cq44Rq88WbQcB1VOpMA6bxdfZxfYQ8rgu9Ehpbes,7448
69
69
  arelle/UrlUtil.py,sha256=HrxZSG59EUMGMMGmWPuZkPi5-0BGqY3jAMkp7V4IdZo,32400
70
- arelle/Validate.py,sha256=aAWLqhc5HjFtwopHaM54PayXQFBJsREPG1SI2kSjyAw,58181
70
+ arelle/Validate.py,sha256=hIp9qD5n6SrQNGzoRio_rjR-9Nsmzv6UG2phxT_Pcww,58589
71
71
  arelle/ValidateDuplicateFacts.py,sha256=L556J1Dhz4ZmsMlRNoDCMpFgDQYiryd9vuBYDvE0Aq8,21769
72
72
  arelle/ValidateFilingText.py,sha256=xnXc0xgdNiHQk0eyP7VSSpvw7qr-pRFRwqqoUb569is,54051
73
73
  arelle/ValidateInfoset.py,sha256=Rz_XBi5Ha43KpxXYhjLolURcWVx5qmqyjLxw48Yt9Dg,20396
@@ -125,7 +125,7 @@ arelle/XmlValidateConst.py,sha256=U_wN0Q-nWKwf6dKJtcu_83FXPn9c6P8JjzGA5b0w7P0,33
125
125
  arelle/XmlValidateParticles.py,sha256=Mn6vhFl0ZKC_vag1mBwn1rH_x2jmlusJYqOOuxFPO2k,9231
126
126
  arelle/XmlValidateSchema.py,sha256=6frtZOc1Yrx_5yYF6V6oHbScnglWrVbWr6xW4EHtLQI,7428
127
127
  arelle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
- arelle/_version.py,sha256=PpZpsQ46i8_2AumMdLQ4lTPEnxAAp7fQNliASDQaahI,515
128
+ arelle/_version.py,sha256=xztzzCsSO6dI_xsGmGG8v1j6UZRVyDBfVwKeEk-vY9M,515
129
129
  arelle/typing.py,sha256=PRe-Fxwr2SBqYYUVPCJ3E7ddDX0_oOISNdT5Q97EbRM,1246
130
130
  arelle/api/Session.py,sha256=27HVuK3Bz1_21l4_RLn1IQg6v0MNsUEYrHajymyWwxI,7429
131
131
  arelle/archive/CustomLogger.py,sha256=v_JXOCQLDZcfaFWzxC9FRcEf9tQi4rCI4Sx7jCuAVQI,1231
@@ -222,7 +222,7 @@ arelle/examples/plugin/multi.py,sha256=LPu4BB1wanejVJAT8thLpYIjv4kuCm7nPaGXezzW7
222
222
  arelle/examples/plugin/rssSaveOim.py,sha256=-6pMdjgrcvdKAiTIFK1qDj7El4HFXJIIuCnIna6RAI8,4297
223
223
  arelle/examples/plugin/validate/XYZ/DisclosureSystems.py,sha256=6HxGoe6ioXoRg_0-iDlzckfN3RgS6fJRWivElH1gLFI,72
224
224
  arelle/examples/plugin/validate/XYZ/PluginValidationDataExtension.py,sha256=JoaZ8wubDLTFJe7zbDnKham5vr6r3fOO1VzZ-LdMghY,235
225
- arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py,sha256=jXSkuRi7Q8vBadDRsbH2SZ50XItmKMXwwcMAI8JdQD4,1638
225
+ arelle/examples/plugin/validate/XYZ/ValidationPluginExtension.py,sha256=AAEko9mLTcRPwc4JG1fBkjvzuhBvDpn-tTctwrLLMPQ,1690
226
226
  arelle/examples/plugin/validate/XYZ/__init__.py,sha256=uReCvbrDPUlLRLJckOc1DdJsncNukxHRXZ3O5W0ay_A,2493
227
227
  arelle/examples/plugin/validate/XYZ/resources/config.xml,sha256=lcNWbfGMupREzaWRiuW49sCKmPL5llOtGQ5QZ_avQ0o,682
228
228
  arelle/examples/plugin/validate/XYZ/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -383,7 +383,7 @@ arelle/plugin/validate/CIPC/__init__.py,sha256=R6KVETICUpfW--TvVkFNDo-67Kq_KpWz3
383
383
  arelle/plugin/validate/CIPC/config.xml,sha256=4pyn40JAvQQeoRC8I046gZ4ZcmUnekX3TNfYpC5yonI,667
384
384
  arelle/plugin/validate/DBA/DisclosureSystems.py,sha256=Dp_r-Pa3tahtCfDha2Zc97N0iyrY4Zagb8w2D9ErILg,294
385
385
  arelle/plugin/validate/DBA/PluginValidationDataExtension.py,sha256=R0lNf-3-lKHlnCy4ByU1TT1lVImMe35KeV-mbDxeaco,7290
386
- arelle/plugin/validate/DBA/ValidationPluginExtension.py,sha256=JozGD4LqGn8zbc_-BhZOPMJl0PSboloS6oG8UwwTz6I,48638
386
+ arelle/plugin/validate/DBA/ValidationPluginExtension.py,sha256=UygF7oELnPJcP6Ta0ncy3dy5fnJq-Mz6N2-gEaVhigo,48690
387
387
  arelle/plugin/validate/DBA/__init__.py,sha256=KhmlUkqgsRtEXpu5DZBXFzv43nUTvi-0sdDNRfw5Up4,1564
388
388
  arelle/plugin/validate/DBA/resources/config.xml,sha256=KHfo7SrjzmjHbfwIJBmESvOOjdIv4Av26BCcZxfn3Pg,875
389
389
  arelle/plugin/validate/DBA/rules/__init__.py,sha256=eBm6FAb_WnBBYfgLSwsO30gVjpWaHxLqRHRJAIBj7oo,10418
@@ -395,13 +395,14 @@ arelle/plugin/validate/DBA/rules/tr.py,sha256=4TootFjl0HXsKZk1XNBCyj-vnjRs4lg35h
395
395
  arelle/plugin/validate/EBA/__init__.py,sha256=x3zXNcdSDJ3kHfL7kMs0Ve0Vs9oWbzNFVf1TK4Avmy8,45924
396
396
  arelle/plugin/validate/EBA/config.xml,sha256=37wMVUAObK-XEqakqD8zPNog20emYt4a_yfL1AKubF8,2022
397
397
  arelle/plugin/validate/EDINET/Constants.py,sha256=ENqevcx-d8ZMBbboxbbBnQpbyY-rzHX7_1SSuAaw75s,988
398
- arelle/plugin/validate/EDINET/ControllerPluginData.py,sha256=iPlUAt0-VTYdNDjpLfvZKjt-JJaBCCYt87UpZr_S-sk,2955
398
+ arelle/plugin/validate/EDINET/ControllerPluginData.py,sha256=PrEhllue_lh-sLeUy9w8I9XLMLgpo5eW-c7ZKgNyGLE,6256
399
399
  arelle/plugin/validate/EDINET/DisclosureSystems.py,sha256=3rKG42Eg-17Xx_KXU_V5yHW6I3LTwQunvf4a44C9k_4,36
400
400
  arelle/plugin/validate/EDINET/InstanceType.py,sha256=aLKb4-AJ6nDZKMOLCp7u08E9VD64ExeZy9_oGth-LTk,3207
401
401
  arelle/plugin/validate/EDINET/ManifestInstance.py,sha256=SkQV-aOsYn3CTgCkH4IXNdM3QKoiz8okwb29ftMtV3Q,6882
402
- arelle/plugin/validate/EDINET/PluginValidationDataExtension.py,sha256=c4l3zKdQkCoDtbzdBSeiO_7Z4RJrkPYf-68wPhg9Jpg,10184
403
- arelle/plugin/validate/EDINET/ValidationPluginExtension.py,sha256=XL4M73Vnq4jdQWuYhjDwIGBMtLVXopyNpw3tqYw5s3Q,1769
404
- arelle/plugin/validate/EDINET/__init__.py,sha256=WRLuVDP9GA2uQ_vTbZLJmU6hGUpxyb-Tgd4NvKkz5n0,2850
402
+ arelle/plugin/validate/EDINET/PluginValidationDataExtension.py,sha256=A_iJox9SEWCsSWwhlno6I3VUysdAhFrtsMSNO9CcJ7c,5508
403
+ arelle/plugin/validate/EDINET/UploadContents.py,sha256=L0u5171cLBKX7NT-_szRqOfkiy4Gv1xPsfpPVgPhtu0,409
404
+ arelle/plugin/validate/EDINET/ValidationPluginExtension.py,sha256=oMY0ntLr1qIh3uMi1W_M-bT5bhXPDx048X3oDFP5zOY,2042
405
+ arelle/plugin/validate/EDINET/__init__.py,sha256=OZ7gMknCHd0M-9nt8UOmjEZW50YQzbvSLongr9O7Yi0,3022
405
406
  arelle/plugin/validate/EDINET/resources/config.xml,sha256=7uT4GcRgk5veMLpFhPPQJxbGKiQvM52P8EMrjn0qd0g,646
406
407
  arelle/plugin/validate/EDINET/resources/edinet-taxonomies.xml,sha256=997I3RGTLg5OY3vn5hQxVFAAxOmDSOYpuyQe6VnWSY0,16285
407
408
  arelle/plugin/validate/EDINET/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -410,7 +411,7 @@ arelle/plugin/validate/EDINET/rules/edinet.py,sha256=2PP47pJDz1jgZ4FcWS6fcBU6NIL
410
411
  arelle/plugin/validate/EDINET/rules/frta.py,sha256=N0YglHYZuLD2IuwE26viR2ViwUYjneBuMFU9vlrS0aQ,7616
411
412
  arelle/plugin/validate/EDINET/rules/gfm.py,sha256=4EKMho6eX-Ygl8yMBVabHQpbC-wxMvi067ubN9mp27U,21982
412
413
  arelle/plugin/validate/EDINET/rules/manifests.py,sha256=MoT9R_a4BzuYdQVbF7RC5wz134Ve68svSdJ3NlpO_AU,4026
413
- arelle/plugin/validate/EDINET/rules/upload.py,sha256=XC-8rQFM6t9y025viWR97Y6MCoYlxb3_TlHzmHw_lwo,20342
414
+ arelle/plugin/validate/EDINET/rules/upload.py,sha256=k1o12K_vMN2N5bAXPxLRwyKjghoOGrgfLijE_j_5ilQ,19811
414
415
  arelle/plugin/validate/ESEF/Const.py,sha256=JujF_XV-_TNsxjGbF-8SQS4OOZIcJ8zhCMnr-C1O5Ho,22660
415
416
  arelle/plugin/validate/ESEF/Dimensions.py,sha256=MOJM7vwNPEmV5cu-ZzPrhx3347ZvxgD6643OB2HRnIk,10597
416
417
  arelle/plugin/validate/ESEF/Util.py,sha256=QH3btcGqBpr42M7WSKZLSdNXygZaZLfEiEjlxoG21jE,7950
@@ -429,7 +430,7 @@ arelle/plugin/validate/FERC/config.xml,sha256=bn9b8eCqJA1J62rYq1Nz85wJrMGAahVmmn
429
430
  arelle/plugin/validate/FERC/resources/ferc-utr.xml,sha256=OCRj9IUpdXATCBXKbB71apYx9kxcNtZW-Hq4s-avsRY,2663
430
431
  arelle/plugin/validate/NL/DisclosureSystems.py,sha256=urRmYJ8RnGPlTgSVKW7zGN4_4CtL3OVKlcI3LwTpBz4,561
431
432
  arelle/plugin/validate/NL/PluginValidationDataExtension.py,sha256=OswvJzy5AdvIDw1N_IeWDehKbUHdOFnipy0qhjAaImw,33678
432
- arelle/plugin/validate/NL/ValidationPluginExtension.py,sha256=h6CjGJJkE8YqrzPiA8uNaCzn_P6HspH-6ja89tLCXxY,17978
433
+ arelle/plugin/validate/NL/ValidationPluginExtension.py,sha256=GCeR6xh1Dd7IhFoQk5XD6Hg2bg9PyyWEWp5GzxUdUq4,18070
433
434
  arelle/plugin/validate/NL/__init__.py,sha256=W-SHohiAWM7Yi77gAbt-D3vvZNAB5s1j16mHCTFta6w,3158
434
435
  arelle/plugin/validate/NL/resources/config.xml,sha256=qBE6zywFSmemBSWonuTII5iuOCUlNb1nvkpMbsZb5PM,1853
435
436
  arelle/plugin/validate/NL/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -440,7 +441,7 @@ arelle/plugin/validate/NL/rules/fr_nl.py,sha256=vwzk6_P6jhszpeboTM09uOHtm-Lijzz7
440
441
  arelle/plugin/validate/NL/rules/nl_kvk.py,sha256=bPD5lfr6dxdVGPZ2D_bEZtbz02tzsX_BZ5kC-CZJNwc,90170
441
442
  arelle/plugin/validate/ROS/DisclosureSystems.py,sha256=rJ81mwQDYTi6JecFZ_zhqjjz3VNQRgjHNSh0wcQWAQE,18
442
443
  arelle/plugin/validate/ROS/PluginValidationDataExtension.py,sha256=nCZoGWNX4aUN5oQBJqa8UrG9tElPW906xkfzRlbjBB8,4285
443
- arelle/plugin/validate/ROS/ValidationPluginExtension.py,sha256=HrLoJWHMFjou3j7ro6Ajrcab1JOgi505ZFPEM5a3QOY,776
444
+ arelle/plugin/validate/ROS/ValidationPluginExtension.py,sha256=OEDpi3CqcHrlX87UTG2KEYHnQrcbo8iajBY8X8nF6Tw,829
444
445
  arelle/plugin/validate/ROS/__init__.py,sha256=KuWg1MHVzA2S6eaHFptvP3Vu_5gQWf3OUYC97clB7zI,2075
445
446
  arelle/plugin/validate/ROS/config.xml,sha256=ZCpCFgr1ZAjoUuhb1eRpDnmKrae-sXA9yl6SOWnrfm8,654
446
447
  arelle/plugin/validate/ROS/resources/config.xml,sha256=HXWume5HlrAqOx5AtiWWqgADbRatA8YSfm_JvZGwdgQ,657
@@ -751,21 +752,21 @@ arelle/utils/Contexts.py,sha256=j9uSBAXGkunlJGC9SscCbb1cj3oU_J3b_yjdhj2B4a4,1714
751
752
  arelle/utils/EntryPointDetection.py,sha256=4RzercL0xE4PJrwoeUYq3S-E7PMSD-IspyS9bwK2RYM,4722
752
753
  arelle/utils/Equivalence.py,sha256=Ac6sENvh-WHJlBJneV_-6n_MF2C1filHETkUEiucLJg,525
753
754
  arelle/utils/PluginData.py,sha256=GUnuZaApm1J4Xm9ZA1U2M1aask-AaNGviLtc0fgXbFg,265
754
- arelle/utils/PluginHooks.py,sha256=CeVxti23VjERQl4xWFucDVTW63TCG2PUdnxpjd3x_Ms,31170
755
+ arelle/utils/PluginHooks.py,sha256=SX0aH5KnP2E_m5nL_mwn1o0uBTMgG0TeMXfG_HuUe00,32323
755
756
  arelle/utils/Units.py,sha256=c9bwnu9Xnm00gC9Q6qQ1ogsEeTEXGRH7rahlEbrEWnQ,1201
756
757
  arelle/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
757
758
  arelle/utils/validate/Decorator.py,sha256=8LGmA171HZgKrALtsMKyHqMNM-XCdwJOv6KpZz4pC2c,3161
758
759
  arelle/utils/validate/DetectScriptsInXhtml.py,sha256=RFBh_Z24OjR69s71qQzSzbxdU-WCTWuvYlONN-BgpZ0,2098
759
760
  arelle/utils/validate/ESEFImage.py,sha256=M3pR9zxz0Y8oNjrpniEYwztCV2hoBK4fDSi4U095C3k,15520
760
761
  arelle/utils/validate/Validation.py,sha256=n6Ag7VeCj_VO5nqzw_P53hOfXXeT2APK0Enb3UQqBns,832
761
- arelle/utils/validate/ValidationPlugin.py,sha256=_WeRPXZUTCcSN3FLbFwiAe_2pAUTxZZk1061qMoDW8w,11527
762
+ arelle/utils/validate/ValidationPlugin.py,sha256=LUlH24tSq1W_eFP3DQZfByFPEKpgdlOaTXKiIWQltSc,13658
762
763
  arelle/utils/validate/ValidationUtil.py,sha256=9vmSvShn-EdQy56dfesyV8JjSRVPj7txrxRFgh8FxIs,548
763
764
  arelle/utils/validate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
764
765
  arelle/webserver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
765
766
  arelle/webserver/bottle.py,sha256=P-JECd9MCTNcxCnKoDUvGcoi03ezYVOgoWgv2_uH-6M,362
766
- arelle_release-2.37.45.dist-info/licenses/LICENSE.md,sha256=Q0tn6q0VUbr-NM8916513NCIG8MNzo24Ev-sxMUBRZc,3959
767
- arelle_release-2.37.45.dist-info/METADATA,sha256=IzXpgdPmfhmjqvo9OBzx7jdAMDfjjO97Li1C29T5kw0,9137
768
- arelle_release-2.37.45.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
769
- arelle_release-2.37.45.dist-info/entry_points.txt,sha256=Uj5niwfwVsx3vaQ3fYj8hrZ1xpfCJyTUA09tYKWbzpo,111
770
- arelle_release-2.37.45.dist-info/top_level.txt,sha256=fwU7SYawL4_r-sUMRg7r1nYVGjFMSDvRWx8VGAXEw7w,7
771
- arelle_release-2.37.45.dist-info/RECORD,,
767
+ arelle_release-2.37.47.dist-info/licenses/LICENSE.md,sha256=Q0tn6q0VUbr-NM8916513NCIG8MNzo24Ev-sxMUBRZc,3959
768
+ arelle_release-2.37.47.dist-info/METADATA,sha256=YqUy_T7g_QnlcZG9bw4jw9RKuriEGVtBnn_vkEqrdnw,9137
769
+ arelle_release-2.37.47.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
770
+ arelle_release-2.37.47.dist-info/entry_points.txt,sha256=Uj5niwfwVsx3vaQ3fYj8hrZ1xpfCJyTUA09tYKWbzpo,111
771
+ arelle_release-2.37.47.dist-info/top_level.txt,sha256=fwU7SYawL4_r-sUMRg7r1nYVGjFMSDvRWx8VGAXEw7w,7
772
+ arelle_release-2.37.47.dist-info/RECORD,,