diff --git a/render/Inline.py b/render/Inline.py index 662f9865..c99b6a4f 100644 --- a/render/Inline.py +++ b/render/Inline.py @@ -10,7 +10,6 @@ (c) Copyright 2013 Mark V Systems Limited, All rights reserved. ''' from arelle import ModelXbrl, ValidateXbrlDimensions, XbrlConst -from arelle.PluginManager import pluginClassMethods from arelle.PrototypeDtsObject import LocPrototype, ArcPrototype from arelle.ModelDocument import ModelDocument, ModelDocumentReference, Type, load from arelle.ModelInstanceObject import ModelInlineFootnote @@ -114,7 +113,7 @@ def saveTargetDocument(filing, modelXbrl, targetDocumentFilename, targetDocument targetUrl = targetUrlParts[0] + suffix + targetUrlParts[2] if suplSuffix: targetUrl += suplSuffix modelXbrl.modelManager.showStatus(_("Extracting instance ") + os.path.basename(targetUrl)) - for pluginXbrlMethod in pluginClassMethods("InlineDocumentSet.CreateTargetInstance"): + for pluginXbrlMethod in modelXbrl.modelManager.cntlr.plugins.hooks("InlineDocumentSet.CreateTargetInstance"): targetInstance = pluginXbrlMethod(modelXbrl, targetUrl, targetDocumentSchemaRefs, filingFiles, # no lang on xbrl:xbrl, specific xml:lang on elements which aren't en-US baseXmlLang=None, defaultXmlLang="en-US", skipInvalid=True) diff --git a/render/__init__.py b/render/__init__.py index 5d61d6a7..8920206f 100644 --- a/render/__init__.py +++ b/render/__init__.py @@ -147,11 +147,10 @@ from collections import defaultdict from arelle import PythonUtil -from arelle import (Cntlr, FileSource, ModelDocument, XmlUtil, Version, ModelValue, Locale, PluginManager, WebCache, ModelFormulaObject, Validate, +from arelle import (Cntlr, FileSource, ModelDocument, XmlUtil, Version, ModelValue, Locale, WebCache, ModelFormulaObject, Validate, ViewFileFactList, ViewFileFactTable, ViewFileConcepts, ViewFileFormulae, ViewFileRelationshipSet, ViewFileTests, ViewFileRssFeed, ViewFileRoleTypes) from arelle.ModelInstanceObject import ModelFact, ModelInlineFootnote -from arelle.PluginManager import pluginClassMethods from arelle.ValidateFilingText import elementsWithNoContent from arelle.XhtmlValidate import xhtmlValidate from arelle.XmlValidateConst import VALID, NONE, UNVALIDATED @@ -722,7 +721,7 @@ def setProcessingFolder(self, filesource, entryPointFile=None): else: _url = filesource.url # filesource.url may have an inline document set, trim it off - for pluginXbrlMethod in pluginClassMethods("InlineDocumentSet.Url.Separator"): + for pluginXbrlMethod in filesource.hooks("InlineDocumentSet.Url.Separator"): inlineDocSetSeparator = pluginXbrlMethod() _url = _url.partition(inlineDocSetSeparator)[0] self.processingFolder = os.path.dirname(_url) @@ -1192,7 +1191,7 @@ def copyResourceToReportFolder(filename, additionalDirectory=None): IoManager.writeXmlDoc(filing, rootETree, self.reportZip, self.reportsFolder, "PrivateFilingSummary.xml" if hasPrivateData and self.isWorkstationFirstPass else'FilingSummary.xml') # generate supplemental AllReports and other such outputs at this time - for supplReport in pluginClassMethods("EdgarRenderer.FilingEnd.SupplementalReport"): + for supplReport in cntlr.plugins.hooks("EdgarRenderer.FilingEnd.SupplementalReport"): supplReport(cntlr, filing, self.reportsFolder) # if there's a dissem directory and no logs, remove summary logs if (hasPrivateData or @@ -1367,7 +1366,7 @@ def copyResourceToReportFolder(filename, additionalDirectory=None): self.logDebug("Arelle viewer generated {:.3f} secs.".format(time.time() - _startedAt)) if self.isWorkstationFirstPass and not hasPrivateData: _startedAt = time.time() - for generate in pluginClassMethods("iXBRLViewer.Generate"): + for generate in cntlr.plugins.hooks("iXBRLViewer.Generate"): generate(cntlr, dissemReportsFolder, "/arelleViewer-1.4.11/ixbrlviewer.js", useStubViewer="ixbrlviewer.xhtml.dissem", saveStubOnly=True) self.logDebug("Arelle viewer for dissemination generated {:.3f} secs.".format(time.time() - _startedAt)) self.logDebug(Summary.FilingSummaryCompletionMessage) @@ -1554,7 +1553,7 @@ def copyResourceToReportFolder(filename, additionalDirectory=None): elif self.isWorkstationFirstPass: # remove first pass excel report pass # no excel output if no disseminated reports # generate supplemental AllReports and other such outputs at this time - for supplReport in pluginClassMethods("EdgarRenderer.FilingEnd.SupplementalReport"): + for supplReport in cntlr.plugins.hooks("EdgarRenderer.FilingEnd.SupplementalReport"): supplReport(cntlr, filing, dissemReportsFolder, zipDir="dissem/") if generateiXBRLViewerStub: _startedAt = time.time() diff --git a/render/iXBRLViewerInterface.py b/render/iXBRLViewerInterface.py index c346e792..6cd96c69 100644 --- a/render/iXBRLViewerInterface.py +++ b/render/iXBRLViewerInterface.py @@ -12,8 +12,6 @@ """ import io, os, sys, zipfile -from arelle.PluginManager import pluginClassMethods -from arelle import PluginManager _iXBRLViewerPlugin = None _iXBRLViewer_plugin_info = None @@ -26,7 +24,7 @@ def hasIXBRLViewerPlugin(cntlr): global _iXBRLViewerPlugin if _iXBRLViewerPlugin is not None: return True - if "ixbrl-viewer" not in PluginManager.pluginConfig["modules"]: + if "ixbrl-viewer" not in cntlr.plugins.get_plugins(): return False try: from arelle.plugin import iXBRLViewerPlugin as _iXBRLViewerPlugin @@ -40,7 +38,7 @@ def generateViewer(cntlr, stubDir): stubPath = os.path.join(stubDir, STUB_NAME) securityIsActive = securityHasWritten = False stubBytes = None - for pluginMethod in pluginClassMethods("Security.Crypt.IsActive"): + for pluginMethod in cntlr.plugins.hooks("Security.Crypt.IsActive"): securityIsActive = pluginMethod(self) # must be active for the save method to save encrypted files _iXBRLViewerPlugin.pluginData(cntlr).builder = _iXBRLViewerPlugin.IXBRLViewerBuilder(cntlr, useStubViewer = True) _iXBRLViewerPlugin.processModel(cntlr, cntlr.modelManager.modelXbrl) @@ -61,14 +59,19 @@ def generateViewer(cntlr, stubDir): if not stubBytes: return if securityIsActive: - for pluginMethod in pluginClassMethods("Security.Crypt.Write"): + for pluginMethod in cntlr.plugins.hooks("Security.Crypt.Write"): securityHasWritten = pluginMethod(self, stubPath, stubBytes) if not securityHasWritten: with open(stubPath, "wb") as fout: fout.write(stubBytes) def disableiXBRLViewerPluginInfo(cntlr): - if PluginManager.pluginConfig["modules"].get("ixbrl-viewer", {}).get("status", "disabled") == "enabled": - PluginManager.pluginConfig["modules"]["ixbrl-viewer"]["status"] = "disabled" - PluginManager.reset() - cntlr.addToLog(_("iXBRLViewer plugin disabled for EdgarRenderer. EdgarRenderer manages iXBRLViewer within its workflow.")) + pluginMeta = cntlr.plugins.handles.get_plugins().get("ixbrl-viewer") + if pluginMeta is None: + return + if pluginMeta.status == "enabled": + cntlr.addToLog( + _("iXBRLViewer plugin should not be enabled for EdgarRenderer. " + "EdgarRenderer manages iXBRLViewer within its workflow."), + messageCode="arelle.ixbrlViewerPluginEnabled", + ) diff --git a/validate/Filing.py b/validate/Filing.py index 6649eba9..eaac6146 100644 --- a/validate/Filing.py +++ b/validate/Filing.py @@ -22,7 +22,6 @@ from arelle.ModelInstanceObject import ModelFact, ModelInlineFact, ModelInlineFootnote from arelle.ModelDtsObject import ModelConcept, ModelResource from arelle.ModelXbrl import NONDEFAULT -from arelle.PluginManager import pluginClassMethods from arelle.PrototypeDtsObject import LinkPrototype, LocPrototype, ArcPrototype from arelle.PythonUtil import pyNamedObject, strTruncate, normalizeSpace, lcStr, flattenSequence, flattenToSet, OrderedSet from arelle.UrlUtil import isHttpUrl @@ -145,8 +144,10 @@ def validateFiling(val, modelXbrl, isEFM=False, isGFM=False): modelXbrl.isLoggingEffectiveFor(level="WARNING-SEMANTIC") or modelXbrl.isLoggingEffectiveFor(level="ERROR-SEMANTIC")) + cntlr = modelXbrl.modelManager.cntlr + if isEFM: - for pluginXbrlMethod in pluginClassMethods("Validate.EFM.Start"): + for pluginXbrlMethod in cntlr.plugins.hooks("Validate.EFM.Start"): pluginXbrlMethod(val) if "EFM/Filing.py#validateFiling_start" in val.modelXbrl.arelleUnitTests: @@ -168,7 +169,7 @@ def validateFiling(val, modelXbrl, isEFM=False, isGFM=False): dqcRules = {} isInlineXbrl = modelXbrl.modelDocument.type in (ModelDocument.Type.INLINEXBRL, ModelDocument.Type.INLINEXBRLDOCUMENTSET) isXbrlInstance = isInlineXbrl or modelXbrl.modelDocument.type == ModelDocument.Type.INSTANCE - isFtJson = any(pluginXbrlMethod(modelXbrl) for pluginXbrlMethod in pluginClassMethods("FtJson.IsFtJsonDocument")) + isFtJson = any(pluginXbrlMethod(modelXbrl) for pluginXbrlMethod in cntlr.plugins.hooks("FtJson.IsFtJsonDocument")) if isEFM: if not attachmentDocumentType or not hasSubmissionType: # unspecified submission parameters (from cmd line or formula parameters dialog) isFeeTagging = any(doc.targetNamespace.startswith("http://xbrl.sec.gov/ffd/") for doc in modelXbrl.urlDocs.values() if doc.targetNamespace) @@ -602,7 +603,7 @@ def hasDeiFact(deiName): extractedCoverFacts[f.qname.localName].append(f) if isEFM: # note that this is in the "if context is not None" region. It does receive nil facts. - for pluginXbrlMethod in pluginClassMethods("Validate.EFM.Fact"): + for pluginXbrlMethod in cntlr.plugins.hooks("Validate.EFM.Fact"): pluginXbrlMethod(val, f) #6.5.17 facts with precision concept = f.concept @@ -5735,7 +5736,7 @@ def sumItemChildren(fromNames, toNames): raise pyNamedObject(val.modelXbrl.arelleUnitTests["EFM/Filing.py#validateFiling_end"], "EFM/Filing.py#validateFiling_end") if isEFM: - for pluginXbrlMethod in pluginClassMethods("Validate.EFM.Finally"): + for pluginXbrlMethod in cntlr.plugins.hooks("Validate.EFM.Finally"): pluginXbrlMethod(val, conceptsUsed) val.modelXbrl.profileActivity("... plug in '.Finally' checks", minTimeToShow=1.0) val.modelXbrl.profileStat(_("validate{0}").format(modelXbrl.modelManager.disclosureSystem.validationType)) diff --git a/validate/XuleInterface.py b/validate/XuleInterface.py index 411c88e2..7599efcd 100644 --- a/validate/XuleInterface.py +++ b/validate/XuleInterface.py @@ -37,9 +37,9 @@ """ import optparse, os, json import regex as re +from arelle.plugin_system.plugin_meta import PluginMeta import traceback import sys -from arelle import PluginManager from arelle.PythonUtil import attrdict, pyNamedObject from .Util import usgaapYear @@ -68,7 +68,7 @@ ")") """Do not change anything below this line.""" -_xule_plugin_info = None +_xule_plugin_info: PluginMeta | None = None xuleValidateFinally = None xulePluginDoesNotExist = False user_defined_xule_rule_set = False @@ -101,8 +101,8 @@ def close(cntlr): # unhook Xule's 'Validate.Finally' from validate/EFM global xuleValidateFinally ''' if xuleValidateFinally is not None: - PluginManager.modulePluginInfos[getXulePlugin(cntlr)["name"]]['Validate.Finally'] = xuleValidateFinally # restore original finally - PluginManager.reset() + cntlr._pluginManager.modulePluginInfos[getXulePlugin(cntlr).name]['Validate.Finally'] = xuleValidateFinally # restore original finally + cntlr._pluginManager.reset() xuleValidateFinally = None ''' def blockXuleValidateFinally(val): @@ -314,10 +314,10 @@ def getXulePlugin(cntlr): """ global _xule_plugin_info, _incompatible_plugin, xulePluginDoesNotExist if _xule_plugin_info is None and not xulePluginDoesNotExist: - for plugin_info in PluginManager.modulePluginInfos.values(): - moduleUrl = plugin_info.get('moduleURL') + for plugin_meta in cntlr.plugins.get_plugins().values(): + moduleUrl = plugin_meta.module_url if moduleUrl.endswith('xule'): - _xule_plugin_info = plugin_info + _xule_plugin_info = plugin_meta elif DQC_plugin_url_pattern.match(moduleUrl): _incompatible_plugin = moduleUrl cntlr.addToLog(_("EDGAR is not compatible with the DQC.py plugin, please remove the DQC.py plugin. The EDGAR plugin directly manages running of to run DQC rules."), @@ -336,10 +336,8 @@ def getXuleMethod(cntlr, class_name): Get a method/function from the Xule plugin. This is how this validator calls functions in the Xule plugin. """ - xule_plugin = getXulePlugin(cntlr) - if xule_plugin is not None: - return xule_plugin.get(class_name) - return None + __ = getXulePlugin(cntlr) # Triggers logs if xule plugin is not available. + return next(cntlr.plugins.hooks(class_name), None) def menuTools(cntlr, menu): """Add validator menu the Tools menu in the Arelle GUI diff --git a/validate/__init__.py b/validate/__init__.py index cf08750a..55a45484 100644 --- a/validate/__init__.py +++ b/validate/__init__.py @@ -153,7 +153,6 @@ from arelle.ModelDocument import Type from arelle.ModelInstanceObject import ModelFact from arelle.ModelValue import qname -from arelle.PluginManager import pluginClassMethods # , pluginMethodsForClasses, modulePluginInfos from arelle.PythonUtil import flattenSequence from arelle.UrlUtil import authority, relativeUri from arelle.ValidateFilingText import referencedFiles @@ -455,18 +454,18 @@ def filingStart(cntlr, options, filesource, entrypointFiles, sourceZipStream=Non # cntlr.addToLog("TRACE EFM filing start 2 classes={} moduleInfos={}".format(pluginMethodsForClasses, modulePluginInfos)) modelManager.efmFiling = Filing(cntlr, options, filesource, entrypointFiles, sourceZipStream, responseZipStream) # this event is called for filings (of instances) as well as test cases, for test case it just keeps options accessible - for pluginXbrlMethod in pluginClassMethods("EdgarRenderer.Filing.Start"): + for pluginXbrlMethod in cntlr.plugins.hooks("EdgarRenderer.Filing.Start"): pluginXbrlMethod(cntlr, options, entrypointFiles, modelManager.efmFiling) # check if any entrypointFiles have an encryption is specified if isinstance(entrypointFiles, list): - for pluginXbrlMethod in pluginClassMethods("Security.Crypt.Filing.Start"): + for pluginXbrlMethod in cntlr.plugins.hooks("Security.Crypt.Filing.Start"): pluginXbrlMethod(modelManager.efmFiling, options, filesource, entrypointFiles, sourceZipStream) def guiTestcasesStart(cntlr, modelXbrl, *args, **kwargs): modelManager = cntlr.modelManager if cntlr.hasGui: # enable EdgarRenderer to initiate ixviewer irregardless of whether an EFM disclosure system is active - for pluginXbrlMethod in pluginClassMethods("EdgarRenderer.Gui.Run"): + for pluginXbrlMethod in cntlr.plugins.hooks("EdgarRenderer.Gui.Run"): xuleInit(cntlr) pluginXbrlMethod(cntlr, modelXbrl, *args, # pass plugin items to GUI mode of EdgarRenderer @@ -531,7 +530,7 @@ def xbrlRun(cntlr, options, modelXbrl, *args, **kwargs): efmFiling = modelManager.efmFiling _report = efmFiling.getReport(modelXbrl) if _report is not None: # HF TESTING: not (options.abortOnMajorError and len(modelXbrl.errors) > 0): - for pluginXbrlMethod in pluginClassMethods("EdgarRenderer.Xbrl.Run"): + for pluginXbrlMethod in cntlr.plugins.hooks("EdgarRenderer.Xbrl.Run"): pluginXbrlMethod(cntlr, options, modelXbrl, modelManager.efmFiling, _report) def filingValidate(cntlr, options, filesource, entrypointFiles, sourceZipStream=None, responseZipStream=None, *args, **kwargs): @@ -582,7 +581,7 @@ def filingEnd(cntlr, options, filesource, entrypointFiles, sourceZipStream=None, #cntlr.addToLog("TRACE EFM filing end") modelManager = cntlr.modelManager if hasattr(modelManager, "efmFiling"): - for pluginXbrlMethod in pluginClassMethods("EdgarRenderer.Filing.End"): + for pluginXbrlMethod in cntlr.plugins.hooks("EdgarRenderer.Filing.End"): pluginXbrlMethod(cntlr, options, filesource, modelManager.efmFiling, sourceZipStream=sourceZipStream) #cntlr.addToLog("TRACE EdgarRenderer end") # save JSON file of instances and referenced documents @@ -616,7 +615,7 @@ def testcaseVariationXbrlLoaded(testcaseModelXbrl, instanceModelXbrl, modelTestc if not hasattr(modelManager, "efmFiling"): # first instance of filing modelManager.efmFiling = Filing(cntlr, options, instanceModelXbrl.fileSource, entrypointFiles, None, None, instanceModelXbrl.errorCaptureLevel) # this event is called for filings (of instances) as well as test cases, for test case it just keeps options accessible - for pluginXbrlMethod in pluginClassMethods("EdgarRenderer.Filing.Start"): + for pluginXbrlMethod in cntlr.plugins.hooks("EdgarRenderer.Filing.Start"): pluginXbrlMethod(cntlr, options, entrypointFiles, modelManager.efmFiling) xuleInit(cntlr) modelManager.efmFiling.addReport(instanceModelXbrl) @@ -637,7 +636,7 @@ def testcaseVariationXbrlValidated(testcaseModelXbrl, instanceModelXbrl, *args, efmFiling = modelManager.efmFiling _report = efmFiling.getReport(instanceModelXbrl) if _report is not None: # HF TESTING: not (options.abortOnMajorError and len(modelXbrl.errors) > 0): - for pluginXbrlMethod in pluginClassMethods("EdgarRenderer.Xbrl.Run"): + for pluginXbrlMethod in modelManager.cntlr.plugins.hooks("EdgarRenderer.Xbrl.Run"): pluginXbrlMethod(modelManager.cntlr, efmFiling.options, instanceModelXbrl, efmFiling, _report) def testcaseVariationValidated(testcaseModelXbrl, instanceModelXbrl, errors=None, *args, **kwargs): @@ -660,7 +659,7 @@ def testcaseVariationValidated(testcaseModelXbrl, instanceModelXbrl, errors=None def fileSourceFile(cntlr, filepath, binary, stripDeclaration): modelManager = cntlr.modelManager if hasattr(modelManager, "efmFiling"): - for pluginXbrlMethod in pluginClassMethods("Security.Crypt.FileSource.File"): + for pluginXbrlMethod in cntlr.plugins.hooks("Security.Crypt.FileSource.File"): _file = pluginXbrlMethod(cntlr, modelManager.efmFiling, filepath, binary, stripDeclaration) if _file is not None: return _file @@ -669,7 +668,7 @@ def fileSourceFile(cntlr, filepath, binary, stripDeclaration): def fileSourceExists(cntlr, filepath): modelManager = cntlr.modelManager if hasattr(modelManager, "efmFiling"): - for pluginXbrlMethod in pluginClassMethods("Security.Crypt.FileSource.Exists"): + for pluginXbrlMethod in cntlr.plugins.hooks("Security.Crypt.FileSource.Exists"): _existence = pluginXbrlMethod(modelManager.efmFiling, filepath) if _existence is not None: return _existence @@ -728,7 +727,7 @@ def __init__(self, cntlr, options=None, filesource=None, entrypointfiles=None, s self.errorCaptureLevel = errorCaptureLevel or logging._checkLevel("INCONSISTENCY") self.errors = [] self.arelleUnitTests = {} # copied from each instance loaded - for pluginXbrlMethod in pluginClassMethods("Security.Crypt.Init"): + for pluginXbrlMethod in cntlr.plugins.hooks("Security.Crypt.Init"): pluginXbrlMethod(self, options, filesource, entrypointfiles, sourceZipStream) self.exhibitTypesStrippingOnErrorPattern = exhibitTypesStrippingOnErrorPattern self.exhibitTypesPrivateNotDisseminated = exhibitTypesPrivateNotDisseminated @@ -807,7 +806,7 @@ def hasInlineReport(self): def writeFile(self, filepath, data): # write the data (string or binary) - for pluginXbrlMethod in pluginClassMethods("Security.Crypt.Write"): + for pluginXbrlMethod in self.cntlr.plugins.hooks("Security.Crypt.Write"): if pluginXbrlMethod(self, filepath, data): return with io.open(filepath, "wt" if isinstance(data, str) else "wb") as fh: