Package: toadstool
ToadstoolPython load tools -- An opinionated solution for cleanly loading files directly into Python objects.This library works by creating custom Python finders and loaders and injecting them into the Python meta search path.The Python import system is explained in great detailhere.Consider ajsonfilesample.json{"employee":{"name":"sonoo","salary":56000,"married":true},"menu":{"id":"file","value":"File","popup":{"menuitem":[{"value":"New","onclick":"CreateDoc()"},{"value":"Open","onclick":"OpenDoc()"},{"value":"Save","onclick":"SaveDoc()"}]}}}can be loaded and used in the following way:importtoadstoolimportsample# Or import using ToadContextfromtoadstool.utils.utilsimportToadContextfromtoadstool.loaders.json_loaderimportJsonLoaderwithToadContext(JsonLoader):importsample>>>sample.sample.employeesample.jsonsample.menuprint(sample.menu)>{'id':'file','value':'File','popup':{'menuitem':[{'value':'New','onclick':'CreateDoc()'},{'value':'Open','onclick':'OpenDoc()'},{'value':'Save','onclick':'SaveDoc()'}]}}RequirementsRequires Python 3.10InstallBasic Install$ python3 -m pip install toadstoolAdvanced Install$ python3 -m pip install toadstool[options]Whereoptionsare any of the following (or combinations thereof):tomlFor.tomlsupport usingtomllibrary for Python versions prior toPython 3.11, which introducedtomllibinto the standard libraryyamlFor.yamlsupport usingPyYamlgqlFor.gqlor.graphqlquery support usinggraphql-corelibraryUsageTo use this module, simplyimport toadstoolbefore any import you wish to use the included loaders for. This can be at the top of your source file or just before any imports for the supported filetypes.LoadersGraphQL (.gql | .graphql)Load graphql queries directly as graphql.language.ast.DocumentNode objects from theGraphQL Core Library.  Allows direct importing of queries/mutations/subscriptions/fragments (aka GraphQL operations).Example:Given a GraphQL query file namesqueries.graphlorqueries.gqlwith the following contents:queryHeroComparison($first:Int=3){leftComparison:hero(episode:EMPIRE){...comparisonFields}rightComparison:hero(episode:JEDI){...comparisonFields}}fragmentcomparisonFieldsonCharacter{namefriendsConnection(first:$first){totalCountedges{node{name}}}}mutationCreateReviewForEpisode($ep:Episode!,$review:ReviewInput!){createReview(episode:$ep,review:$review){starscommentary}}then you can import the contents of the file either as a whole module:importqueriesprint(queries.__dict__)>{'__name__':'queries','__doc__':None,'__package__':'','__loader__':GqlImporter('queries.gql'),'__spec__':ModuleSpec(name='queries',loader=GqlImporter('queries.gql')),'HeroComparison':DocumentNode,'operations':{'HeroComparison':DocumentNode},'__file__':'queries.gql'}or using specific query names:fromqueriesimportHeroComparisonprint(HeroComparison.definitions)>(OperationDefinitionNodeat0:180,FragmentDefinitionNodeat182:339)Also tracks all operations in a module dict asqueries.operationsJSON (.json)Loads JSON objects using builtinjsonlibrary. The top-level JSON keys are stored as attirbutes for the module and the wholejsonconverteddictis stored asimported_name.jsonFor example, the following filesample.json{"employee":{"name":"sonoo","salary":56000,"married":true},"menu":{"id":"file","value":"File","popup":{"menuitem":[{"value":"New","onclick":"CreateDoc()"},{"value":"Open","onclick":"OpenDoc()"},{"value":"Save","onclick":"SaveDoc()"}]}}}can be loaded and used in the following way:importtoadstoolimportsample>>>sample.sample.employeesample.jsonsample.menuprint(sample.menu)>{'id':'file','value':'File','popup':{'menuitem':[{'value':'New','onclick':'CreateDoc()'},{'value':'Open','onclick':'OpenDoc()'},{'value':'Save','onclick':'SaveDoc()'}]}}You also have the json root object available atsample.json:importtoadstoolimportsampleprint(sample.json)>{'employee':{'name':'sonoo','salary':56000,'married':True},'menu':{'id':'file','value':'File','popup':{'menuitem':[{'value':'New','onclick':'CreateDoc()'},{'value':'Open','onclick':'OpenDoc()'},{'value':'Save','onclick':'SaveDoc()'}]}}}TOML (.toml)Loads TOML files such that each top-level table becomes an attribute of the imported module. Also loads the whole TOML file as a dictionary under thetomlattirbute (which will overwrite any table from the file with the nametomlas well). For example, if you haveexample.tomlwith the following contents:[project]name='Toadstool'description='Python Load Tools Suite'readme='README.md'requires-python="~=3.10"license={file='LICENSE'}version='0.1.0'authors=[{name='Andrés Alejos',email='acalejos@proton.me'}]classifiers=['License :: OSI Approved :: MIT Licens','Programming Language :: Python :: 3','Topic :: Software Development','Topic :: Utilities',]keywords=["import","loader","meta","sys"]urls={Home="https://github.com/acalejos/toadstool"}[project.optional-dependencies]gql=['graphql_core>=3.2.3']yaml=['pyyaml >= 5.3.1']toml=['toml >= 0.10.2;python_version < "3.11"']all=['toadstool[gql]','toadstool[yaml]','toadstool[toml]'][sample]name='Sample'[[Root]]name='Root'can be loaded and used in the following way:importtoadstoolimportexample>>>example.example.Rootexample.projectexample.sampleexample.tomlYAML (.yaml | .yml)Loads a YAML file, assigning each top-level key as a module attribute (similar to the JSON loader). Also loads the whole YAML definition as adictinto theyamlattirbute. If the YAML file has more than 1 YAML definition (which is legal within the YAML definition), then only theyamlattribute is set, and is alist[dict]. Suppose you have a YAML file calledsample.yamlwith the following contents:document:1name:'erik'then you could do the following:importtoadstoolimportsample>>>sample.sample.documentsample.namesample.yamland suppose you had a similar multifile YAML file:document:1name:'erik'---document:2name:'config'then you could do the following:importtoadstoolimportsample>>>sample.sample.yamlConfig (.ini | .cfg | .config)Uses theconfigparserpackage from the standard library to load the file and assign any top-level key to the module's attribute, and the entireConfigParserobject to theconfigattribute. Suppose you have the following config filesample.ini:[http]port=8080username=httpuser[https]port=8043username=httpsuser[FTP]port=8043username=ftpuser[database]driverclass=com.mysql.jdbc.DriverdbName=mydatabaseport=3306username=rootthen you could do the followingimporttoadstoolimportsample>>>sample.sample.httpsample.httpssample.FTPsample.databasesample.configCSV (.csv)Loads a CSV file and loads the contents intorowsandcolumnsattributes aslist[list[str]].  This will attempt to determine if the CSV contains a header as the first row, and will do the following according to that determination:If Header:Creates attributesnamed_rowsandnamed_columnsnamed_columns: Each column is adictkeyed on the column header and contains the whole columnnamed_rows: Each row contains adictwith each column entry keyed from its header to the value at that row.Populates each column name as an attribute name that will correspond to its respective columnPopulates an attributefieldnameswith the header values.For any CSV:Creates arowsattirbute which contains alist[list[str]]with the CSV content rows (skips header if exists)Creates acolumnsattirbute which contains alist[list[str]]with the CSV content columnsSuppose you have a file calledsample.csvwith the following contents:Index,Customer Id,First Name
1,DD37Cf93aecA6Dc,Sheryl
2,1Ef7b82A4CAAD10,Preston
3,6F94879bDAfE5a6,Roy
4,5Cef8BFA16c5e3c,Linda
5,053d585Ab6b3159,Joannathen you can do the following:importtoadstoolimportsample>>>sample.sample.First_Namesample.named_rowssample.rowssample.Indexsample.columnssample.named_columnssample.Customer_Idsample.fieldnamesToadContextLocated intoadstool.utils.utils, ToadContext is a Context Manager to allow imports withtoadstoolloaders without permanently changing thesys.meta_path.  Using this, you can import your files the same way as you would usingimport toadstoolfrom within a context without having to actually importtoadstool. You must explicitly pass loaders you wish to use as arguments.This context manager does not yield anything, so the proper usage is:withToadContext(Loader|list(Loaders)):importmy_moduleLimitationsToadstool works by injecting the supportedLoaders into thesys.meta_pathduring Toadstool's module init. Thesys.meta_pathis a list of class instances that contain thefind_specandexec_modulemethods. This occurs the first time that Toadstool is imported (uponinitof Toadstool), after whichtoadstoolis cached in thesys.modulesdata structure, which is what is checked when any module is referenced before deferring to the loaders insys.meta_pathand searching for the module. Therefore, after the firstimport toadstoolthat occurs per Python interpreter session, the module__init__.pymight not be run again and thus if you do anything to alter thesys.meta_pathafterimport toadstoolthen theLoaders may no longer be in the search path.One possible solution is to useimportlib.reload(toadstool)which reruns the module__init__.pycode (only code not contained inif __name__ == '__main__'block).Another possible solution is to use theToadContextwhen you wish to import using the ToadstoolLoaders, This could prove to be redundant, but it also ensures that thesys.meta_pathis only altered while importing the modules that requiretoadstooland removes theLoaders upon existing the context.ContributingFeel free to open feature requests for new features or simply supported file extensions. If you feel so inclined, you can also open up a pull request to merge in your own loader. Creating a new loader is very straight forwards and can be accomplished by:Creating a file intoadstool/loaderswith the name{filetype}_loader.pyImplementing a class that inherits from theLoaderclass located intoadstool/loaders/base_loader. Your class must implement the following:Add a class attributefile_extsspecifying your file extension(s) either as astrfor a single extension orlist[str]for multiple extensions. For example:classCsvLoader(Loader):file_exts="csv"classGqlLoader(Loader):file_exts=["gql","graphql"]Definedef exec_module(self, module):where your functionality will be implemented. This will typically look like updating themodule.__dict__with values according to how you load the fileIf your loader has dependencies:Specify a new optional dependency inpyrpoject.tomlunder[project.optional-dependencies]Add your dependency to thealloptional dependency by adding it astoadstool[your_dep]Add your loader to the listall_loadersin the root__init__.pyfile as a 3-tuple with:Path to your module in dotted notationName of your loader className of dependency frompyrpoject.tomlif it exists orNoneRefer to any of the existing loaders intoadstool/loadersfor examples