├── .gitignore ├── .idea ├── .gitignore ├── conceptnet-lite.iml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.md ├── conceptnet_lite ├── __init__.py ├── db.py └── utils.py ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── _static │ └── erd.svg │ ├── conceptnet_lite.rst │ ├── conf.py │ └── index.rst ├── examples ├── conceptnet_queries.py └── download_and_load_dump_to_db.py ├── poetry.lock ├── pyproject.toml └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.csv 3 | pip-wheel-metadata/ 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # Environments 89 | .env 90 | .venv 91 | env/ 92 | venv/ 93 | ENV/ 94 | env.bak/ 95 | venv.bak/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | 110 | # Created by https://www.gitignore.io/api/pycharm 111 | # Edit at https://www.gitignore.io/?templates=pycharm 112 | 113 | ### PyCharm ### 114 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 115 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 116 | 117 | # User-specific stuff 118 | .idea/**/workspace.xml 119 | .idea/**/tasks.xml 120 | .idea/**/usage.statistics.xml 121 | .idea/**/dictionaries 122 | .idea/**/shelf 123 | 124 | # Generated files 125 | .idea/**/contentModel.xml 126 | 127 | # Sensitive or high-churn files 128 | .idea/**/dataSources/ 129 | .idea/**/dataSources.ids 130 | .idea/**/dataSources.local.xml 131 | .idea/**/sqlDataSources.xml 132 | .idea/**/dynamic.xml 133 | .idea/**/uiDesigner.xml 134 | .idea/**/dbnavigator.xml 135 | 136 | # Gradle 137 | .idea/**/gradle.xml 138 | .idea/**/libraries 139 | 140 | # Gradle and Maven with auto-import 141 | # When using Gradle or Maven with auto-import, you should exclude module files, 142 | # since they will be recreated, and may cause churn. Uncomment if using 143 | # auto-import. 144 | # .idea/modules.xml 145 | # .idea/*.iml 146 | # .idea/modules 147 | # *.iml 148 | # *.ipr 149 | 150 | # CMake 151 | cmake-build-*/ 152 | 153 | # Mongo Explorer plugin 154 | .idea/**/mongoSettings.xml 155 | 156 | # File-based project format 157 | *.iws 158 | 159 | # IntelliJ 160 | out/ 161 | 162 | # mpeltonen/sbt-idea plugin 163 | .idea_modules/ 164 | 165 | # JIRA plugin 166 | atlassian-ide-plugin.xml 167 | 168 | # Cursive Clojure plugin 169 | .idea/replstate.xml 170 | 171 | # Crashlytics plugin (for Android Studio and IntelliJ) 172 | com_crashlytics_export_strings.xml 173 | crashlytics.properties 174 | crashlytics-build.properties 175 | fabric.properties 176 | 177 | # Editor-based Rest Client 178 | .idea/httpRequests 179 | 180 | # Android studio 3.1+ serialized cache file 181 | .idea/caches/build_file_checksums.ser 182 | 183 | ### PyCharm Patch ### 184 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 185 | 186 | # *.iml 187 | # modules.xml 188 | # .idea/misc.xml 189 | # *.ipr 190 | 191 | # Sonarlint plugin 192 | .idea/sonarlint 193 | 194 | # End of https://www.gitignore.io/api/pycharm 195 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Default ignored files 3 | /workspace.xml -------------------------------------------------------------------------------- /.idea/conceptnet-lite.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # conceptnet-lite [maintainance mode] 2 | [![License](https://img.shields.io/pypi/l/conceptnet-lite.svg)](https://www.apache.org/licenses/LICENSE-2.0) 3 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/conceptnet-lite.svg) 4 | [![PyPI](https://img.shields.io/pypi/v/conceptnet-lite.svg)](https://pypi.org/project/conceptnet-lite/) 5 | [![Documentation Status](https://img.shields.io/readthedocs/conceptnet-lite.svg)](http://conceptnet-lite.readthedocs.io/en/latest/) 6 | 7 | Conceptnet-lite is a Python library for working with ConceptNet offline without the need for PostgreSQL. 8 | 9 | The library comes with Apache License 2.0, and is separate from ConceptNet itself. The ConceptNet is available under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/) license, which also applies to the formatted database file that we provide. See [here](https://github.com/commonsense/conceptnet5/wiki/Copying-and-sharing-ConceptNet) for the list of conditions for using ConceptNet data. 10 | 11 | This is the official citation for ConceptNet if you use it in research: 12 | 13 | > Robyn Speer, Joshua Chin, and Catherine Havasi. 2017. "ConceptNet 5.5: An Open Multilingual Graph of General Knowledge." In proceedings of AAAI 31. 14 | 15 | ## Status 16 | 17 | ConceptNet was not updated since September 2021, so Conceptnet-lite won't be updated either. Please do not use it in any new projects. 18 | 19 | ## Installation 20 | 21 | To install `conceptnet-lite` use `pip`: 22 | 23 | ```shell 24 | $ pip install conceptnet-lite 25 | ``` 26 | 27 | ## Connecting to the database 28 | 29 | Before you can use `conceptnet-lite`, you will need to obtain ConceptNet dabase file. You have two options: download pre-made one or build it yourself from the raw ConceptNet assertions file. 30 | 31 | ### Downloading the ConceptNet database 32 | 33 | ConceptNet releases happen once a year. You can use `conceptnet-lite` to build your own database from the raw assertions file (see below), but if there is a pre-built file it will be faster to just get that one. `conceptnet-lite` can download and unpack it to the specified folder automatically. 34 | 35 | Here is a [link](https://conceptnet-lite.fra1.cdn.digitaloceanspaces.com/conceptnet.db.zip) to a compressed database for ConceptNet 5.7. This link is used automatically if you do not supply the alternative. 36 | 37 | ```python 38 | import conceptnet_lite 39 | 40 | conceptnet_lite.connect("/path/to/conceptnet.db") 41 | ``` 42 | 43 | This command both downloads the resource (our build for ConceptNet 5.7) and connects to the database. If path specified as the first argument does not exist, it will be created (unless there is a permissions problem). Note that the database file is quite large (over 9 Gb). 44 | 45 | If your internet connection is intermittent, the built-in download function may give you errors. If so, just download the file separately, unpack it to the directory of your choice and provide the path to the `.connect()` method as described below. 46 | 47 | ### Building the database for a new release. 48 | 49 | If a database file is not found in the folder specified in the `db_path` argument, `conceptnet-lite` will attempt to automatically download the raw assertions file from [here](https://github.com/commonsense/conceptnet5/wiki/Downloads) and build the database. This takes a couple of hours, so we recommend getting the pre-built file. 50 | 51 | If you provide a path, this is where the database will be built. Note that the database file is quite large (over 9 Gb). Note that you need to pass `db_download_url=None` to force the library build the database from dump. 52 | 53 | ```python 54 | import conceptnet_lite 55 | 56 | conceptnet_lite.connect("/path/to/conceptnet.db", db_download_url=None) 57 | ``` 58 | 59 | If the specified does not exist, it will be created (unless there is a permissions problem). If no path is specified, and no database file is not found in the current working directory, `conceptnet-lite` will attempt to build one in the current working directory. 60 | 61 | Once the database is built, `conceptnet-lite` will connect to it automatically. 62 | 63 | ### Loading the ConceptNet database 64 | 65 | Once you have the database file, all you need to do is to pass the path to it to the `.connect()` method. 66 | 67 | ```python 68 | import conceptnet_lite 69 | 70 | conceptnet_lite.connect("/path/to/conceptnet.db") 71 | ``` 72 | 73 | If no path is specified, `conceptnet-lite` will check if a database file exists in the current working directory. If it is not found, it will trigger the process of downloading the pre-built database (see above). 74 | 75 | ## Accessing concepts 76 | 77 | Concepts objects are created by looking for every entry that matches the input string exactly. 78 | If none is found, the `peewee.DoesNotExist` exception will be raised. 79 | 80 | ```python 81 | from conceptnet_lite import Label 82 | 83 | cat_concepts = Label.get(text='cat').concepts 84 | for c in cat_concepts: 85 | print(" Concept URI:", c.uri) 86 | print(" Concept text:", c.text) 87 | ``` 88 | ```console 89 | Concept URI: /c/en/cat 90 | Concept text: cat 91 | Concept URI: /c/en/cat/n 92 | Concept text: cat 93 | Concept URI: /c/en/cat/n/wn/animal 94 | Concept text: cat 95 | Concept URI: /c/en/cat/n/wn/person 96 | ... 97 | ``` 98 | 99 | `concept.uri` provides access to ConceptNet URIs, as described [here](https://github.com/commonsense/conceptnet5/wiki/URI-hierarchy). You can also retrieve only the text of the entry by `concept.text`. 100 | 101 | ## Working with languages 102 | 103 | You can limit the languages to search for matches. Label.get() takes an optional `language` attribute that is expected to be an instance `Language`, which in turn is created by calling `Language.get()` with `name` argument. 104 | List of available languages and their codes are described [here](https://github.com/commonsense/conceptnet5/wiki/Languages). 105 | 106 | ```python 107 | from conceptnet_lite import Label 108 | 109 | cat_concepts = Label.get(text='cat', language='en').concepts 110 | for c in cat_concepts: 111 | print(" Concept URI:", c.uri) 112 | print(" Concept text:", c.text) 113 | print(" Concept language:", c.language.name) 114 | ``` 115 | 116 | ```console 117 | Concept URI: /c/en/cat 118 | Concept text: cat 119 | Concept language: en 120 | Concept URI: /c/en/cat/n 121 | Concept text: cat 122 | Concept language: en 123 | Concept URI: /c/en/cat/n/wn/animal 124 | Concept text: cat 125 | Concept language: en 126 | Concept URI: /c/en/cat/n/wn/person 127 | Concept text: cat 128 | Concept language: en 129 | ... 130 | ``` 131 | 132 | 133 | ## Querying edges between concepts 134 | 135 | To retrieve the set of relations between two concepts, you need to create the concept objects (optionally specifying the language as described above). `cn.edges_between()` method retrieves all edges between the specified concepts. You can access its URI and a number of attributes, as shown below. 136 | 137 | Some ConceptNet relations are symmetrical: for example, the antonymy between *white* and *black* works both ways. Some relations are asymmetrical: e.g. the relation between *cat* and *mammal* is either hyponymy or hyperonymy, depending on the direction. The `two_way` argument lets you choose whether the query should be symmetrical or not. 138 | 139 | ```python 140 | from conceptnet_lite import Label, edges_between 141 | 142 | introvert_concepts = Label.get(text='introvert', language='en').concepts 143 | extrovert_concepts = Label.get(text='extrovert', language='en').concepts 144 | for e in edges_between(introvert_concepts, extrovert_concepts, two_way=False): 145 | print(" Edge URI:", e.uri) 146 | print(" Edge name:", e.relation.name) 147 | print(" Edge start node:", e.start.text) 148 | print(" Edge end node:", e.end.text) 149 | print(" Edge metadata:", e.etc) 150 | ``` 151 | ```console 152 | Edge URI: /a/[/r/antonym/,/c/en/introvert/n/,/c/en/extrovert/] 153 | Edge name: antonym 154 | Edge start node: introvert 155 | Edge end node: extrovert 156 | Edge metadata: {'dataset': '/d/wiktionary/en', 'license': 'cc:by-sa/4.0', 'sources': [{'contributor': '/s/resource/wiktionary/en', 'process': '/s/process/wikiparsec/2'}, {'contributor': '/s/resource/wiktionary/fr', 'process': '/s/process/wikiparsec/2'}], 'weight': 2.0} 157 | ``` 158 | 159 | * **e.relation.name**: the name of ConceptNet relation. Full list [here](https://github.com/commonsense/conceptnet5/wiki/Relations). 160 | 161 | * **e.start.text, e.end.text**: the source and the target concepts in the edge 162 | 163 | * **e.etc**: the ConceptNet [metadata](https://github.com/commonsense/conceptnet5/wiki/Edges) dictionary contains the source dataset, sources, weight, and license. For example, the introvert:extrovert edge for English contains the following metadata: 164 | 165 | ```json 166 | { 167 | "dataset": "/d/wiktionary/en", 168 | "license": "cc:by-sa/4.0", 169 | "sources": [{ 170 | "contributor": "/s/resource/wiktionary/en", 171 | "process": "/s/process/wikiparsec/2" 172 | }, { 173 | "contributor": "/s/resource/wiktionary/fr", 174 | "process": "/s/process/wikiparsec/2" 175 | }], 176 | "weight": 2.0 177 | } 178 | ``` 179 | 180 | ## Accessing all relations for a given concepts 181 | 182 | You can also retrieve all relations between a given concepts and all other concepts, with the same options as above: 183 | 184 | ```python 185 | from conceptnet_lite import Label, edges_for 186 | 187 | for e in edges_for(Label.get(text='introvert', language='en').concepts, same_language=True): 188 | print(e.start.text, "::", e.end.text, "|", e.relation.name) 189 | ``` 190 | ```console 191 | extrovert :: introvert | antonym 192 | introvert :: extrovert | antonym 193 | outrovert :: introvert | antonym 194 | reflection :: introvert | at_location 195 | introverse :: introvert | derived_from 196 | introversible :: introvert | derived_from 197 | introversion :: introvert | derived_from 198 | introversion :: introvert | derived_from 199 | introversive :: introvert | derived_from 200 | introverted :: introvert | derived_from 201 | ... 202 | ``` 203 | 204 | The same set of edge attributes are available for `edges_between` and `edges_for` (e.uri, e.relation.name, e.start.text, e.end.text, e.etc). 205 | 206 | Note that we have used optional argument `same_language=True`. By supplying this argument we make `edges_for` return 207 | relations, both ends of which are in the same language. If this argument is skipped it is possible to get edges to 208 | concepts in languages other than the source concepts language. For example, the same command as above with `same_language=False` will include the following in the output: 209 | 210 | ```console 211 | kääntyä_sisäänpäin :: introvert | synonym 212 | sulkeutua :: introvert | synonym 213 | sulkeutunut :: introvert | synonym 214 | introverti :: introvert | synonym 215 | asociale :: introvert | synonym 216 | introverso :: introvert | synonym 217 | introvertito :: introvert | synonym 218 | 内向 :: introvert | synonym 219 | ``` 220 | 221 | ## Accessing concept edges with a given relation direction 222 | 223 | You can also query the relations that have a specific concept as target or source. This is achieved with `concept.edges_out` and `concept.edges_in`, as follows: 224 | 225 | ```python 226 | from conceptnet_lite import Label 227 | 228 | concepts = Label.get(text='introvert', language='en').concepts 229 | for c in concepts: 230 | print(" Concept text:", c.text) 231 | if c.edges_out: 232 | print(" Edges out:") 233 | for e in c.edges_out: 234 | print(" Edge URI:", e.uri) 235 | print(" Relation:", e.relation.name) 236 | print(" End:", e.end.text) 237 | if c.edges_in: 238 | print(" Edges in:") 239 | for e in c.edges_in: 240 | print(" Edge URI:", e.uri) 241 | print(" Relation:", e.relation.name) 242 | print(" End:", e.end.text) 243 | ``` 244 | ```console 245 | Concept text: introvert 246 | Edges out: 247 | Edge URI: /a/[/r/etymologically_derived_from/,/c/en/introvert/,/c/la/introvertere/] 248 | Relation: etymologically_derived_from 249 | End: introvertere 250 | ... 251 | Edges in: 252 | Edge URI: /a/[/r/antonym/,/c/cs/extrovert/n/,/c/en/introvert/] 253 | Relation: antonym 254 | End: introvert 255 | ... 256 | ``` 257 | 258 | ## Traversing all the data for a language 259 | 260 | You can go over all concepts for a given language. For illustration, let us try Old Norse, a "small" language with the code "non" and vocab size of 7868, according to the [ConceptNet language statistics](https://github.com/commonsense/conceptnet5/wiki/Languages). 261 | 262 | ```python 263 | from conceptnet_lite import Language 264 | 265 | mylanguage = Language.get(name='non') 266 | for l in mylanguage.labels: 267 | print(" Label:", l.text) 268 | for c in l.concepts: 269 | print(" Concept URI:", c.uri) 270 | if c.edges_out: 271 | print(" Edges out:") 272 | for e in c.edges_out: 273 | print(" Edge URI:", e.uri) 274 | if c.edges_in: 275 | print(" Edges in:") 276 | for e in c.edges_in: 277 | print(" Edge URI:", e.uri) 278 | ``` 279 | ```console 280 | Label: andsœlis 281 | Concept URI: /c/non/andsœlis/r 282 | Edges out: 283 | Edge URI: /a/[/r/antonym/,/c/non/andsœlis/r/,/c/non/réttsœlis/] 284 | Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/against/] 285 | Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/course/] 286 | Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/sun/] 287 | Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/widdershins/] 288 | Edge URI: /a/[/r/synonym/,/c/non/andsœlis/r/,/c/non/rangsœlis/] 289 | Concept URI: /c/non/andsœlis 290 | Edges out: 291 | Edge URI: /a/[/r/external_url/,/c/non/andsœlis/,/c/en.wiktionary.org/wiki/andsœlis/] 292 | Label: réttsœlis 293 | Concept URI: /c/non/réttsœlis 294 | Edges in: 295 | Edge URI: /a/[/r/antonym/,/c/non/andsœlis/r/,/c/non/réttsœlis/] 296 | ... 297 | ``` 298 | 299 | ## Accessing Concepts by URI 300 | 301 | You can access concept ORM objects directly by providing a desired ConceptNet URI. This is done as follows: 302 | 303 | ```python 304 | from conceptnet_lite import Concept 305 | 306 | edge_object = Edge.get(start='/c/en/example') 307 | concept_object = Concept.get(uri='/c/en/example') 308 | ``` 309 | -------------------------------------------------------------------------------- /conceptnet_lite/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pathlib import Path 3 | from typing import Iterable, Optional, Union 4 | 5 | import peewee 6 | 7 | from conceptnet_lite.db import CONCEPTNET_EDGE_COUNT, CONCEPTNET_DUMP_DOWNLOAD_URL, CONCEPTNET_DB_NAME 8 | from conceptnet_lite.db import CONCEPTNET_DB_URL 9 | from conceptnet_lite.db import Concept, Language, Label, Relation, RelationName, Edge 10 | from conceptnet_lite.db import prepare_db, _open_db, _generate_db_path, download_db 11 | from conceptnet_lite.utils import PathOrStr, _to_snake_case 12 | 13 | 14 | def connect( 15 | db_path: PathOrStr = CONCEPTNET_DB_NAME, 16 | db_download_url: Optional[str] = CONCEPTNET_DB_URL, 17 | delete_compressed_db: bool = True, 18 | dump_download_url: str = CONCEPTNET_DUMP_DOWNLOAD_URL, 19 | load_dump_edge_count: int = CONCEPTNET_EDGE_COUNT, 20 | delete_compressed_dump: bool = True, 21 | delete_dump: bool = True, 22 | ) -> None: 23 | """Connect to ConceptNet database. 24 | 25 | This function connects to ConceptNet database. If it does not exists, there are two options: to download ready 26 | database or to download the compressed ConceptNet dump, extract it, and load it 27 | into database (pass `db_download_url=None` for this option). 28 | 29 | Args: 30 | db_path: Path to the database. 31 | db_download_url: Link to compressed ConceptNet database. Pass `None` to build the db from dump. 32 | delete_compressed_db: Delete compressed database after extraction. 33 | dump_download_url: Link to compressed ConceptNet dump. 34 | load_dump_edge_count: Number of edges to load from the beginning of the dump file. Can be useful for testing. 35 | delete_compressed_dump: Delete compressed dump after unpacking. 36 | delete_dump: Delete dump after loading into database. 37 | """ 38 | db_path = Path(db_path).expanduser().resolve() 39 | if db_path.is_dir(): 40 | db_path = _generate_db_path(db_path) 41 | try: 42 | if db_path.is_file(): 43 | _open_db(path=db_path) 44 | else: 45 | raise FileNotFoundError(2, "No such file", str(db_path)) 46 | except FileNotFoundError: 47 | print(f"File not found: {db_path}") 48 | if db_download_url is not None: 49 | download_db( 50 | url=db_download_url, 51 | db_path=db_path, 52 | delete_compressed_db=delete_compressed_db, 53 | ) 54 | _open_db(db_path) 55 | else: 56 | prepare_db( 57 | db_path=db_path, 58 | dump_download_url=dump_download_url, 59 | load_dump_edge_count=load_dump_edge_count, 60 | delete_compressed_dump=delete_compressed_dump, 61 | delete_dump=delete_dump, 62 | ) 63 | 64 | 65 | def _get_where_clause_for_relation(relation: Optional[Union[Relation, str]] = None) -> Union[peewee.Expression, bool]: 66 | if relation is None: 67 | return True 68 | else: 69 | if isinstance(relation, str): 70 | relation = Relation.get(name=relation) 71 | return Edge.relation == relation 72 | 73 | 74 | class PartOfEdge(Enum): 75 | ANY = 'any' 76 | START = 'start' 77 | END = 'end' 78 | 79 | 80 | def edges( 81 | part_of_edge: PartOfEdge, 82 | concepts: Iterable[Concept], 83 | relation: Optional[Union[Relation, str]] = None, 84 | same_language: bool = False, 85 | ) -> peewee.ModelSelect: 86 | concepts = list(concepts) 87 | ConceptAlias = Concept.alias() 88 | if part_of_edge == PartOfEdge.ANY: 89 | in_concepts_where_clause = Edge.start.in_(concepts) | Edge.end.in_(concepts) 90 | concept_alias_join_on = ( 91 | (Edge.end.in_(concepts) & (ConceptAlias.id == Edge.start)) | 92 | (Edge.start.in_(concepts) & (ConceptAlias.id == Edge.end))) 93 | elif part_of_edge == PartOfEdge.START: 94 | in_concepts_where_clause = Edge.start.in_(concepts) 95 | concept_alias_join_on = ConceptAlias.id == Edge.end 96 | elif part_of_edge == PartOfEdge.END: 97 | in_concepts_where_clause = Edge.end.in_(concepts) 98 | concept_alias_join_on = ConceptAlias.id == Edge.start 99 | else: 100 | assert False, "Unknown part_of_edge" 101 | result = (Edge.select() 102 | .where(in_concepts_where_clause & _get_where_clause_for_relation(relation))) 103 | if same_language: 104 | cte = result.cte('result') 105 | result = (Edge.select() 106 | .join(cte, on=(Edge.id == cte.c.id)) 107 | .join(ConceptAlias, on=concept_alias_join_on) 108 | .join(Label) 109 | .join(Language) 110 | .where(Language.id == concepts[0].label.language) 111 | .with_cte(cte)) 112 | return result 113 | 114 | 115 | def edges_from( 116 | start_concepts: Iterable[Concept], 117 | relation: Optional[Union[Relation, str]] = None, 118 | same_language: bool = False, 119 | ) -> peewee.ModelSelect: 120 | return edges( 121 | part_of_edge=PartOfEdge.START, 122 | concepts=start_concepts, 123 | relation=relation, 124 | same_language=same_language) 125 | 126 | 127 | def edges_to( 128 | end_concepts: Iterable[Concept], 129 | relation: Optional[Union[Relation, str]] = None, 130 | same_language: bool = False, 131 | ) -> peewee.ModelSelect: 132 | return edges( 133 | part_of_edge=PartOfEdge.END, 134 | concepts=end_concepts, 135 | relation=relation, 136 | same_language=same_language) 137 | 138 | 139 | def edges_for( 140 | concepts: Iterable[Concept], 141 | relation: Optional[Union[Relation, str]] = None, 142 | same_language: bool = False, 143 | ) -> peewee.ModelSelect: 144 | return edges( 145 | part_of_edge=PartOfEdge.ANY, 146 | concepts=concepts, 147 | relation=relation, 148 | same_language=same_language) 149 | 150 | 151 | def edges_between( 152 | start_concepts: Iterable[Concept], 153 | end_concepts: Iterable[Concept], 154 | relation: Optional[Union[Relation, str]] = None, 155 | two_way: bool = False, 156 | ) -> peewee.ModelSelect: 157 | condition = Edge.start.in_(start_concepts) & Edge.end.in_(end_concepts) 158 | if two_way: 159 | condition |= Edge.start.in_(end_concepts) & Edge.end.in_(start_concepts) 160 | condition &= _get_where_clause_for_relation(relation) 161 | return Edge.select().where(condition) 162 | -------------------------------------------------------------------------------- /conceptnet_lite/db.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import gzip 3 | import shutil 4 | import struct 5 | import zipfile 6 | from functools import partial 7 | from pathlib import Path 8 | from typing import Optional, Generator, Tuple 9 | from uuid import uuid4 10 | 11 | import lmdb 12 | from pySmartDL import SmartDL 13 | from tqdm import tqdm 14 | from peewee import DatabaseProxy, Model, TextField, ForeignKeyField 15 | from playhouse.sqlite_ext import JSONField, SqliteExtDatabase 16 | 17 | from conceptnet_lite.utils import PathOrStr, _to_snake_case 18 | 19 | 20 | class RelationName: 21 | """Names of non-deprecated relations. 22 | 23 | See: https://github.com/commonsense/conceptnet5/wiki/Relations. 24 | """ 25 | 26 | RELATED_TO = 'related_to' 27 | FORM_OF = 'form_of' 28 | IS_A = 'is_a' 29 | PART_OF = 'part_of' 30 | HAS_A = 'has_a' 31 | USED_FOR = 'used_for' 32 | CAPABLE_OF = 'capable_of' 33 | AT_LOCATION = 'at_location' 34 | CAUSES = 'causes' 35 | HAS_SUBEVENT = 'has_subevent' 36 | HAS_FIRST_SUBEVENT = 'has_first_subevent' 37 | HAS_LAST_SUBEVENT = 'has_last_subevent' 38 | HAS_PREREQUISITE = 'has_prerequisite' 39 | HAS_PROPERTY = 'has_property' 40 | MOTIVATED_BY_GOAL = 'motivated_by_goal' 41 | OBSTRUCTED_BY = 'obstructed_by' 42 | DESIRES = 'desires' 43 | CREATED_BY = 'created_by' 44 | SYNONYM = 'synonym' 45 | ANTONYM = 'antonym' 46 | DISTINCT_FROM = 'distinct_from' 47 | DERIVED_FROM = 'derived_from' 48 | SYMBOL_OF = 'symbol_of' 49 | DEFINED_AS = 'defined_as' 50 | MANNER_OF = 'manner_of' 51 | LOCATED_NEAR = 'located_near' 52 | HAS_CONTEXT = 'has_context' 53 | SIMILAR_TO = 'similar_to' 54 | ETYMOLOGICALLY_RELATED_TO = 'etymologically_related_to' 55 | ETYMOLOGICALLY_DERIVED_FROM = 'etymologically_derived_from' 56 | CAUSES_DESIRE = 'causes_desire' 57 | MADE_OF = 'made_of' 58 | RECEIVES_ACTION = 'receives_action' 59 | EXTERNAL_URL = 'external_url' 60 | 61 | 62 | db = DatabaseProxy() 63 | 64 | 65 | class _BaseModel(Model): 66 | class Meta: 67 | database = db 68 | 69 | 70 | class Relation(_BaseModel): 71 | """Relation ORM class. 72 | 73 | See: https://github.com/commonsense/conceptnet5/wiki/Relations. 74 | """ 75 | 76 | name = TextField(unique=True) 77 | 78 | def __str__(self): 79 | return self.uri 80 | 81 | @property 82 | def uri(self) -> str: 83 | return f'/r/{self.name}' 84 | 85 | 86 | class Language(_BaseModel): 87 | """Language ORM class. 88 | 89 | See: https://github.com/commonsense/conceptnet5/wiki/Languages. 90 | """ 91 | 92 | name = TextField(unique=True) 93 | 94 | def __str__(self): 95 | return self.name 96 | 97 | 98 | class Label(_BaseModel): 99 | """Label ORM class. 100 | 101 | :class:`Label` can be seen as a part of :class:`Concept`. :class:`Label` is basically a text on a certain language 102 | (most often, a word). 103 | 104 | This abstraction is not present in the original ConceptNet. Class is introduced for the purposes of normalization. 105 | """ 106 | 107 | text = TextField(index=True) 108 | language = ForeignKeyField(Language, backref='labels', null=True) 109 | 110 | def __str__(self): 111 | language_postfix = ' ({self.language.name})' if self.language is not None else '' 112 | return f'{self.text}{language_postfix}' 113 | 114 | @classmethod 115 | def get(cls, *query, **filters): 116 | if isinstance(filters.get('language'), str): 117 | filters['language'] = Language.get(name=filters['language']) 118 | return super().get(*query, **filters) 119 | 120 | 121 | class Concept(_BaseModel): 122 | """Concept ORM class. 123 | 124 | :class:`Concept` represents node in ConceptNet knowledge graph. It provides properties :attr:`language` and 125 | :attr:`text` that are aliases for corresponding :attr:`Label.language` and :attr:`Label.text` fields. 126 | 127 | This abstraction is not present in the original ConceptNet. Class is introduced for the purposes of normalization. 128 | """ 129 | 130 | label = ForeignKeyField(Label, backref='concepts') 131 | sense_label = TextField() 132 | 133 | def __str__(self): 134 | return self.uri 135 | 136 | @classmethod 137 | def get(cls, *query, **filters): 138 | if 'uri' in filters: 139 | split_uri = filters['uri'].split('/', maxsplit=4) 140 | label = Label.get(text=split_uri[3], language=split_uri[2]) 141 | sense_label = '' if len(split_uri) == 4 else split_uri[4] 142 | return Concept.get(label=label, sense_label=sense_label) 143 | return super().get(*query, **filters) 144 | 145 | @property 146 | def uri(self) -> str: 147 | if self.sense_label != 'url': 148 | ending = f'/{self.sense_label}' if self.sense_label else '' 149 | return f'/c/{self.language.name}/{self.text}{ending}' 150 | else: 151 | return f'{self.text}' 152 | 153 | @property 154 | def language(self) -> Language: 155 | return self.label.language 156 | 157 | @property 158 | def text(self) -> str: 159 | return self.label.text 160 | 161 | 162 | class Edge(_BaseModel): 163 | """Edge ORM class. 164 | 165 | See: https://github.com/commonsense/conceptnet5/wiki/Edges. 166 | 167 | Everything except relation, start, and end nodes is stored in :attr:`etc` field that is plain :class:`dict`. 168 | """ 169 | 170 | relation = ForeignKeyField(Relation, backref='edges') 171 | start = ForeignKeyField(Concept, backref='edges_out') 172 | end = ForeignKeyField(Concept, backref='edges_in') 173 | etc = JSONField() 174 | 175 | def __str__(self): 176 | return self.uri 177 | 178 | @classmethod 179 | def get(cls, *query, **filters): 180 | if isinstance(filters.get('relation'), str): 181 | filters['relation'] = Relation.get(name=filters['relation']) 182 | if isinstance(filters.get('start'), str): 183 | filters['start'] = Concept.get(uri=filters['start']) 184 | if isinstance(filters.get('end'), str): 185 | filters['end'] = Concept.get(uri=filters['end']) 186 | return super().get(*query, **filters) 187 | 188 | @property 189 | def uri(self) -> str: 190 | return f'/a/[{self.relation.uri}/,{self.start.uri}/,{self.end.uri}/]' 191 | 192 | 193 | def _open_db(path: PathOrStr): 194 | db.initialize(SqliteExtDatabase(str(path), pragmas={ 195 | 'synchronous': 0, 196 | 'cache_size': -1024 * 64, 197 | })) 198 | tables = [Relation, Language, Label, Concept, Edge] 199 | db.create_tables(tables) 200 | 201 | 202 | # For ConceptNet 5.7: 203 | CONCEPTNET_DUMP_DOWNLOAD_URL = ( 204 | 'https://s3.amazonaws.com/conceptnet/downloads/2019/edges/conceptnet-assertions-5.7.0.csv.gz') 205 | CONCEPTNET_DB_URL = 'https://filedn.com/l4OYewCIMcOmtnyW43I0CuB/conceptnet.db.zip' 206 | CONCEPTNET_EDGE_COUNT = 34074917 207 | CONCEPTNET_DB_NAME = 'conceptnet.db' 208 | 209 | 210 | def _get_download_destination_path(dir_path: Path, url: str) -> Path: 211 | return dir_path / url.rsplit('/')[-1] 212 | 213 | 214 | def download_dump( 215 | url: str = CONCEPTNET_DUMP_DOWNLOAD_URL, 216 | out_dir_path: PathOrStr = Path.cwd(), 217 | ): 218 | """Download compressed ConceptNet dump. 219 | 220 | Args: 221 | url: Link to the dump. 222 | out_dir_path: Dir where to store dump. 223 | """ 224 | 225 | print("Download compressed dump") 226 | compressed_dump_path = _get_download_destination_path(out_dir_path, url) 227 | if compressed_dump_path.is_file(): 228 | raise FileExistsError(17, "File already exists", str(compressed_dump_path)) 229 | downloader = SmartDL(url, str(compressed_dump_path)) 230 | downloader.start() 231 | 232 | 233 | def extract_compressed_dump( 234 | compressed_dump_path: PathOrStr, 235 | delete_compressed_dump: bool = True, 236 | ): 237 | """Extract compressed ConceptNet dump. 238 | 239 | Args: 240 | compressed_dump_path: Path to compressed dump to extract. 241 | delete_compressed_dump: Delete compressed dump after extraction. 242 | """ 243 | 244 | dump_path = Path(compressed_dump_path).with_suffix('') 245 | try: 246 | with gzip.open(str(compressed_dump_path), 'rb') as f_in: 247 | with open(str(dump_path), 'wb') as f_out: 248 | print("Extract compressed dump (this can take a few minutes)") 249 | shutil.copyfileobj(f_in, f_out) 250 | finally: 251 | if delete_compressed_dump and compressed_dump_path.is_file(): 252 | compressed_dump_path.unlink() 253 | 254 | 255 | def load_dump_to_db( 256 | dump_path: PathOrStr, 257 | db_path: PathOrStr, 258 | edge_count: int = CONCEPTNET_EDGE_COUNT, 259 | delete_dump: bool = True, 260 | ): 261 | """Load dump to database. 262 | 263 | Args: 264 | dump_path: Path to dump to load. 265 | db_path: Path to resulting database. 266 | edge_count: Number of edges to load from the beginning of the dump file. Can be useful for testing. 267 | delete_dump: Delete dump after loading into database. 268 | """ 269 | 270 | def edges_from_dump_by_parts_generator( 271 | count: Optional[int] = None, 272 | ) -> Generator[Tuple[str, str, str, str], None, None]: 273 | with open(str(dump_path), newline='') as f: 274 | reader = csv.reader(f, delimiter='\t') 275 | for i, row in enumerate(reader): 276 | if i == count: 277 | break 278 | yield row[1:5] 279 | 280 | def extract_relation_name(uri: str) -> str: 281 | return _to_snake_case(uri[3:]) 282 | 283 | def get_struct_format(length: int) -> str: 284 | return f'{length}Q' 285 | 286 | def pack_ints(*ints) -> bytes: 287 | return struct.pack(get_struct_format(length=len(ints)), *ints) 288 | 289 | def unpack_ints(buffer: bytes) -> Tuple[int, ...]: 290 | return struct.unpack(get_struct_format(len(buffer) // 8), buffer) 291 | 292 | def language_and_label_in_bytes(concept_uri: str, is_external_url: bool) -> Tuple[bytes, bytes]: 293 | if not is_external_url: 294 | return tuple(x.encode('utf8') for x in concept_uri.split('/', maxsplit=4)[2:4])[:2] 295 | else: 296 | return b'', concept_uri.encode('utf8') 297 | 298 | def normalize() -> None: 299 | """Normalize dump before loading into database using lmdb.""" 300 | 301 | def normalize_relation() -> None: 302 | nonlocal relation_i 303 | 304 | name = extract_relation_name(relation_uri) 305 | relation_b = name.encode('utf8') 306 | relation_exists = txn.get(relation_b, db=relation_db) is not None 307 | if not relation_exists: 308 | relation_i += 1 309 | relation_i_b = pack_ints(relation_i) 310 | txn.put(relation_b, relation_i_b, db=relation_db) 311 | 312 | def normalize_concept(uri: str, is_external_url: bool = False) -> None: 313 | nonlocal language_i, label_i, concept_i 314 | 315 | language_b, label_b = language_and_label_in_bytes(concept_uri=uri, is_external_url=is_external_url) 316 | 317 | if not is_external_url: 318 | language_id_b = txn.get(language_b, db=language_db) 319 | if language_id_b is None: 320 | language_i += 1 321 | language_id_b = pack_ints(language_i) 322 | txn.put(language_b, language_id_b, db=language_db) 323 | 324 | label_language_b = label_b + b'/' + language_b 325 | label_id_b = txn.get(label_language_b, db=label_db) 326 | if label_id_b is None: 327 | label_i += 1 328 | label_id_b = pack_ints(label_i) 329 | txn.put(label_language_b, label_id_b, db=label_db) 330 | 331 | concept_b = uri.encode('utf8') 332 | concept_id_b = txn.get(concept_b, db=concept_db) 333 | if concept_id_b is None: 334 | concept_i += 1 335 | concept_id_b = pack_ints(concept_i) 336 | txn.put(concept_b, concept_id_b, db=concept_db) 337 | 338 | language_i, relation_i, label_i, concept_i = 4 * [0] 339 | if not dump_path.is_file(): 340 | raise FileNotFoundError(2, 'No such file', str(dump_path)) 341 | print('Dump normalization') 342 | edges = enumerate(edges_from_dump_by_parts_generator(count=edge_count)) 343 | for i, (relation_uri, start_uri, end_uri, _) in tqdm(edges, unit=' edges', total=edge_count): 344 | normalize_relation() 345 | normalize_concept(start_uri) 346 | is_end_uri_external_url = extract_relation_name(relation_uri) == RelationName.EXTERNAL_URL 347 | normalize_concept(end_uri, is_external_url=is_end_uri_external_url) 348 | 349 | def insert() -> None: 350 | """Load dump from CSV and lmdb database into database.""" 351 | 352 | def insert_objects_from_edge(): 353 | nonlocal edge_i 354 | 355 | def insert_relation() -> Tuple[int, bool]: 356 | nonlocal relation_i 357 | 358 | name = extract_relation_name(relation_uri) 359 | relation_b = name.encode('utf8') 360 | result_id, = unpack_ints(buffer=txn.get(relation_b, db=relation_db)) 361 | if result_id == relation_i: 362 | db.execute_sql('insert into relation (name) values (?)', (name, )) 363 | relation_i += 1 364 | return result_id, name == RelationName.EXTERNAL_URL 365 | 366 | def insert_concept(uri: str, is_external_url: bool = False) -> int: 367 | nonlocal language_i, label_i, concept_i 368 | 369 | split_uri = uri.split('/', maxsplit=4) 370 | 371 | language_b, label_b = language_and_label_in_bytes(concept_uri=uri, is_external_url=is_external_url) 372 | 373 | if not is_external_url: 374 | language_id, = unpack_ints(buffer=txn.get(language_b, db=language_db)) 375 | if language_id == language_i: 376 | name = split_uri[2] 377 | db.execute_sql('insert into language (name) values (?)', (name, )) 378 | language_i += 1 379 | else: 380 | language_id = None 381 | 382 | label_language_b = label_b + b'/' + language_b 383 | label_id, = unpack_ints(buffer=txn.get(label_language_b, db=label_db)) 384 | if label_id == label_i: 385 | text = split_uri[3] if not is_external_url else uri 386 | params = (text, language_id) 387 | db.execute_sql('insert into label (text, language_id) values (?, ?)', params) 388 | label_i += 1 389 | 390 | concept_b = uri.encode('utf8') 391 | concept_id, = unpack_ints(buffer=txn.get(concept_b, db=concept_db)) 392 | if concept_id == concept_i: 393 | sense_label = ('' if len(split_uri) == 4 else split_uri[4]) if not is_external_url else 'url' 394 | params = (label_id, sense_label) 395 | db.execute_sql('insert into concept (label_id, sense_label) values (?, ?)', params) 396 | concept_i += 1 397 | return concept_id 398 | 399 | def insert_edge() -> None: 400 | params = (relation_id, start_id, end_id, edge_etc) 401 | db.execute_sql('insert into edge (relation_id, start_id, end_id, etc) values (?, ?, ?, ?)', params) 402 | 403 | relation_id, is_external_url = insert_relation() 404 | start_id = insert_concept(uri=start_uri) 405 | end_id = insert_concept(uri=end_uri, is_external_url=is_external_url) 406 | insert_edge() 407 | edge_i += 1 408 | 409 | print('Dump insertion') 410 | relation_i, language_i, label_i, concept_i, edge_i = 5 * [1] 411 | edges = edges_from_dump_by_parts_generator(count=edge_count) 412 | progress_bar = tqdm(unit=' edges', total=edge_count) 413 | finished = False 414 | while not finished: 415 | edge_count_per_insert = 1000000 416 | with db.atomic(): 417 | for _ in range(edge_count_per_insert): 418 | try: 419 | relation_uri, start_uri, end_uri, edge_etc = next(edges) 420 | except StopIteration: 421 | finished = True 422 | break 423 | insert_objects_from_edge() 424 | progress_bar.update() 425 | 426 | GIB = 1 << 30 427 | dump_path = Path(dump_path) 428 | lmdb_db_path = dump_path.parent / f'conceptnet-lmdb-{uuid4()}.db' 429 | env = lmdb.open(str(lmdb_db_path), map_size=4*GIB, max_dbs=5, sync=False, writemap=False) 430 | relation_db = env.open_db(b'relation') 431 | language_db = env.open_db(b'language') 432 | label_db = env.open_db(b'label') 433 | concept_db = env.open_db(b'concept') 434 | try: 435 | with env.begin(write=True) as txn: 436 | normalize() 437 | _open_db(path=db_path) 438 | insert() 439 | finally: 440 | shutil.rmtree(str(lmdb_db_path), ignore_errors=True) 441 | if delete_dump and dump_path.is_file(): 442 | dump_path.unlink() 443 | 444 | 445 | def _generate_db_path(db_dir_path: Path) -> Path: 446 | return db_dir_path / CONCEPTNET_DB_NAME 447 | 448 | 449 | def prepare_db( 450 | db_path: PathOrStr, 451 | dump_download_url: str = CONCEPTNET_DUMP_DOWNLOAD_URL, 452 | load_dump_edge_count: int = CONCEPTNET_EDGE_COUNT, 453 | delete_compressed_dump: bool = True, 454 | delete_dump: bool = True, 455 | ): 456 | """Prepare ConceptNet database. 457 | 458 | This function downloads the compressed ConceptNet dump, extracts it, and loads it into database. First two steps 459 | are optional, and are executed only if needed. 460 | 461 | Args: 462 | db_path: Path to the resulting database. 463 | dump_download_url: Link to compressed ConceptNet dump. 464 | load_dump_edge_count: Number of edges to load from the beginning of the dump file. Can be useful for testing. 465 | delete_compressed_dump: Delete compressed dump after extraction. 466 | delete_dump: Delete dump after loading into database. 467 | """ 468 | 469 | db_path = Path(db_path).expanduser().resolve() 470 | if db_path.is_dir(): 471 | db_path = _generate_db_path(db_path) 472 | if db_path.is_file(): 473 | raise FileExistsError(17, "File already exists and it is not a valid database", str(db_path)) 474 | 475 | print("Prepare database") 476 | compressed_dump_path = _get_download_destination_path(db_path.parent, CONCEPTNET_DUMP_DOWNLOAD_URL) 477 | dump_path = compressed_dump_path.with_suffix('') 478 | 479 | db_path.parent.mkdir(parents=True, exist_ok=True) 480 | 481 | load_dump_to_db_ = partial( 482 | load_dump_to_db, 483 | dump_path=dump_path, 484 | db_path=db_path, 485 | edge_count=load_dump_edge_count, 486 | delete_dump=delete_dump, 487 | ) 488 | extract_compressed_dump_ = partial( 489 | extract_compressed_dump, 490 | compressed_dump_path=compressed_dump_path, 491 | delete_compressed_dump=delete_compressed_dump, 492 | ) 493 | download_dump_ = partial( 494 | download_dump, 495 | url=dump_download_url, 496 | out_dir_path=db_path.parent, 497 | ) 498 | 499 | try: 500 | load_dump_to_db_() 501 | except FileNotFoundError: 502 | try: 503 | extract_compressed_dump_() 504 | load_dump_to_db_() 505 | except FileNotFoundError: 506 | download_dump_() 507 | extract_compressed_dump_() 508 | load_dump_to_db_() 509 | finally: 510 | if delete_compressed_dump and compressed_dump_path.is_file(): 511 | compressed_dump_path.unlink() 512 | if delete_dump and dump_path.is_file(): 513 | dump_path.unlink() 514 | 515 | 516 | def download_db( 517 | url: str = CONCEPTNET_DB_URL, 518 | db_path: PathOrStr = CONCEPTNET_DB_NAME, 519 | delete_compressed_db: bool = True 520 | ) -> None: 521 | """Download compressed ConceptNet dump and extract it. 522 | 523 | Args: 524 | url: Link to compressed ConceptNet database. 525 | db_path: Path to resulting database. 526 | delete_compressed_db: Delete compressed database after extraction. 527 | """ 528 | 529 | print("Download compressed database") 530 | db_path = Path(db_path).expanduser().resolve() 531 | if db_path.is_dir(): 532 | db_path = _generate_db_path(db_path) 533 | if db_path.is_file(): 534 | raise FileExistsError(17, "File already exists", str(db_path)) 535 | compressed_db_path = _get_download_destination_path(db_path.parent, url) 536 | if compressed_db_path.is_file(): 537 | raise FileExistsError(17, "File already exists", str(compressed_db_path)) 538 | downloader = SmartDL(url, str(compressed_db_path)) 539 | downloader.start() 540 | try: 541 | with zipfile.ZipFile(str(compressed_db_path), 'r') as zip_f: 542 | print("Extract compressed database (this can take a few minutes)") 543 | zip_f.extractall(db_path.parent) 544 | if db_path.name != CONCEPTNET_DB_NAME: 545 | Path(db_path.parent / CONCEPTNET_DB_NAME).rename(db_path) 546 | finally: 547 | if delete_compressed_db and compressed_db_path.is_file(): 548 | compressed_db_path.unlink() 549 | -------------------------------------------------------------------------------- /conceptnet_lite/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | from typing import Union 4 | 5 | 6 | PathOrStr = Union[Path, str] 7 | 8 | 9 | def _to_snake_case(s: str) -> str: 10 | regex = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))') 11 | return regex.sub(r'_\1', s).lower() 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | lmdb==1.4.1 2 | peewee==3.10.0 3 | pySmartDL==1.3.2 4 | sphinx-autodoc-typehints==1.7.0 5 | tqdm==4.66.3 6 | -------------------------------------------------------------------------------- /docs/source/_static/erd.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | erd 11 | 12 | 13 | 14 | Concept 15 | 16 | 17 |             18 |            Concept             19 |             20 | id 21 |             22 |             23 | AutoField 24 |             25 |             26 | label 27 |             28 |             29 | ForeignKeyField 30 |             31 |             32 | sense_label 33 |             34 |             35 | TextField 36 |             37 | 38 | 39 | 40 | Label 41 | 42 | 43 |             44 |            Label             45 |             46 | id 47 |             48 |             49 | AutoField 50 |             51 |             52 | text 53 |             54 |             55 | TextField 56 |             57 |             58 | language 59 |             60 |             61 | ForeignKeyField 62 |             63 | 64 | 65 | 66 | Concept:label->Label:id 67 | 68 | 69 | 70 | 71 | 72 | Edge 73 | 74 | 75 |             76 |            Edge             77 |             78 | id 79 |             80 |             81 | AutoField 82 |             83 |             84 | relation 85 |             86 |             87 | ForeignKeyField 88 |             89 |             90 | start 91 |             92 |             93 | ForeignKeyField 94 |             95 |             96 | end 97 |             98 |             99 | ForeignKeyField 100 |             101 |             102 | etc 103 |             104 |             105 | JSONField 106 |             107 | 108 | 109 | 110 | Edge:start->Concept:id 111 | 112 | 113 | 114 | 115 | 116 | Edge:end->Concept:id 117 | 118 | 119 | 120 | 121 | 122 | Relation 123 | 124 | 125 |             126 |            Relation             127 |             128 | id 129 |             130 |             131 | AutoField 132 |             133 |             134 | name 135 |             136 |             137 | TextField 138 |             139 | 140 | 141 | 142 | Edge:relation->Relation:id 143 | 144 | 145 | 146 | 147 | 148 | Language 149 | 150 | 151 |             152 |            Language             153 |             154 | id 155 |             156 |             157 | AutoField 158 |             159 |             160 | name 161 |             162 |             163 | TextField 164 |             165 | 166 | 167 | 168 | Label:language->Language:id 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /docs/source/conceptnet_lite.rst: -------------------------------------------------------------------------------- 1 | conceptnet\_lite 2 | ======================== 3 | 4 | 5 | Module contents 6 | --------------- 7 | 8 | .. automodule:: conceptnet_lite 9 | :members: 10 | :undoc-members: 11 | :show-inheritance: 12 | 13 | 14 | conceptnet\_lite.db module 15 | -------------------------- 16 | 17 | .. automodule:: conceptnet_lite.db 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | database schema 23 | --------------- 24 | 25 | .. image:: _static/erd.svg 26 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | import os 17 | import sys 18 | sys.path.insert(0, os.path.abspath('../..')) 19 | 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'conceptnet-lite' 24 | copyright = '2019, LDT team' 25 | author = 'LDT team' 26 | 27 | # The full version, including alpha/beta/rc tags 28 | release = '0.1.27' 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'sphinx.ext.autodoc', 38 | 'sphinx.ext.napoleon', 39 | 'sphinx_autodoc_typehints', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # List of patterns, relative to source directory, that match files and 46 | # directories to ignore when looking for source files. 47 | # This pattern also affects html_static_path and html_extra_path. 48 | exclude_patterns = [] 49 | 50 | 51 | # -- Options for HTML output ------------------------------------------------- 52 | 53 | # The theme to use for HTML and HTML Help pages. See the documentation for 54 | # a list of builtin themes. 55 | # 56 | html_theme = 'sphinx_rtd_theme' 57 | 58 | # Add any paths that contain custom static files (such as style sheets) here, 59 | # relative to this directory. They are copied after the builtin static files, 60 | # so a file named "default.css" will overwrite the builtin "default.css". 61 | html_static_path = ['_static'] 62 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. conceptnet-lite documentation master file, created by 2 | sphinx-quickstart on Sat Aug 31 19:37:58 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to conceptnet-lite's documentation! 7 | =========================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | conceptnet_lite 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | -------------------------------------------------------------------------------- /examples/conceptnet_queries.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import conceptnet_lite 4 | from conceptnet_lite import Label, Language, edges_for, edges_between 5 | 6 | 7 | base_dir_path = Path('~/conceptnet-lite-data') 8 | conceptnet_lite.connect( 9 | db_path=base_dir_path / 'conceptnet.db', 10 | dump_dir_path=base_dir_path, 11 | ) 12 | 13 | russian = Language.get(name='ru') 14 | 15 | print("All edges between 'рай' и 'ад':") 16 | heaven_concepts = Label.get(text='рай', language=russian).concepts 17 | hell_concepts = Label.get(text='ад', language=russian).concepts 18 | for e in edges_between(heaven_concepts, hell_concepts, two_way=True): 19 | print(" Edge URI:", e.uri) 20 | print(" Relation:", e.relation.name) 21 | 22 | english = Language.get(name='en') 23 | 24 | print("Get edges for 'introvert':") 25 | introvert_concepts = Label.get(text='introvert', language=english).concepts 26 | for e in edges_for(introvert_concepts, same_language=True): 27 | print(" Edge URI:", e.uri) 28 | 29 | print("Traversing Russian:") 30 | for l in russian.labels: 31 | print(" Label:", l.text) 32 | for c in l.concepts: 33 | print(" Concept URI:", c.uri) 34 | print(" Concept language:", c.language.name) 35 | if c.edges_out: 36 | print(" Edges out:") 37 | for e in c.edges_out: 38 | print(" Edge URI:", e.uri) 39 | print(" Relation:", e.relation.name) 40 | print(" End:", e.end.uri) 41 | if c.edges_in: 42 | print(" Edges in:") 43 | for e in c.edges_in: 44 | print(" Edge URI:", e.uri) 45 | print(" Relation:", e.relation.name) 46 | print(" End:", e.end.uri) 47 | -------------------------------------------------------------------------------- /examples/download_and_load_dump_to_db.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import conceptnet_lite 4 | 5 | 6 | base_dir_path = Path('~/conceptnet-lite-data') 7 | conceptnet_lite.connect( 8 | db_path=base_dir_path / 'conceptnet.db', 9 | dump_dir_path=base_dir_path, 10 | delete_dump=False, 11 | delete_compressed_dump=False, 12 | ) 13 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "alabaster" 5 | version = "0.7.13" 6 | description = "A configurable sidebar-enabled Sphinx theme" 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, 11 | {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, 12 | ] 13 | 14 | [[package]] 15 | name = "babel" 16 | version = "2.14.0" 17 | description = "Internationalization utilities" 18 | optional = false 19 | python-versions = ">=3.7" 20 | files = [ 21 | {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, 22 | {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, 23 | ] 24 | 25 | [package.dependencies] 26 | pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} 27 | 28 | [package.extras] 29 | dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] 30 | 31 | [[package]] 32 | name = "certifi" 33 | version = "2024.7.4" 34 | description = "Python package for providing Mozilla's CA Bundle." 35 | optional = false 36 | python-versions = ">=3.6" 37 | files = [ 38 | {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, 39 | {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, 40 | ] 41 | 42 | [[package]] 43 | name = "charset-normalizer" 44 | version = "3.3.2" 45 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 46 | optional = false 47 | python-versions = ">=3.7.0" 48 | files = [ 49 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 50 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 51 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 52 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 53 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 54 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 55 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 56 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 57 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 58 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 59 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 60 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 61 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 62 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 63 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 64 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 65 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 66 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 67 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 68 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 69 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 70 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 71 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 72 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 73 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 74 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 75 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 76 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 77 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 78 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 79 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 80 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 81 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 82 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 83 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 84 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 85 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 86 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 87 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 88 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 89 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 90 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 91 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 92 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 93 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 94 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 95 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 96 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 97 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 98 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 99 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 100 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 101 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 102 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 103 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 104 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 105 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 106 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 107 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 108 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 109 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 110 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 111 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 112 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 113 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 114 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 115 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 116 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 117 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 118 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 119 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 120 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 121 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 122 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 123 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 124 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 125 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 126 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 127 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 128 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 129 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 130 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 131 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 132 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 133 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 134 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 135 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 136 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 137 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 138 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 139 | ] 140 | 141 | [[package]] 142 | name = "colorama" 143 | version = "0.4.6" 144 | description = "Cross-platform colored terminal text." 145 | optional = false 146 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 147 | files = [ 148 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 149 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 150 | ] 151 | 152 | [[package]] 153 | name = "docutils" 154 | version = "0.17.1" 155 | description = "Docutils -- Python Documentation Utilities" 156 | optional = false 157 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 158 | files = [ 159 | {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, 160 | {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, 161 | ] 162 | 163 | [[package]] 164 | name = "idna" 165 | version = "3.7" 166 | description = "Internationalized Domain Names in Applications (IDNA)" 167 | optional = false 168 | python-versions = ">=3.5" 169 | files = [ 170 | {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, 171 | {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, 172 | ] 173 | 174 | [[package]] 175 | name = "imagesize" 176 | version = "1.4.1" 177 | description = "Getting image size from png/jpeg/jpeg2000/gif file" 178 | optional = false 179 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 180 | files = [ 181 | {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, 182 | {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, 183 | ] 184 | 185 | [[package]] 186 | name = "jinja2" 187 | version = "3.1.4" 188 | description = "A very fast and expressive template engine." 189 | optional = false 190 | python-versions = ">=3.7" 191 | files = [ 192 | {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, 193 | {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, 194 | ] 195 | 196 | [package.dependencies] 197 | MarkupSafe = ">=2.0" 198 | 199 | [package.extras] 200 | i18n = ["Babel (>=2.7)"] 201 | 202 | [[package]] 203 | name = "lmdb" 204 | version = "1.4.1" 205 | description = "Universal Python binding for the LMDB 'Lightning' Database" 206 | optional = false 207 | python-versions = "*" 208 | files = [ 209 | {file = "lmdb-1.4.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:dcdbe27f75da9b8f58815c6ac9a1f8fa2d7a8d42abc22abb664e089002d5ffa4"}, 210 | {file = "lmdb-1.4.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:032ce6f490caedbec642fc0a79114475e8520d1bf1e1465c6a12b8e5fe39022f"}, 211 | {file = "lmdb-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13c5c8504d419039d6617cee24941e420d648a5b15c4b21e6491821400e5750f"}, 212 | {file = "lmdb-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f8018a947608c4be0dc885c90f477a600be1b71285059a9c68280d36b3fb29b"}, 213 | {file = "lmdb-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:360ac42a8772f571fdd01156e0466d6be52eea1140556a138281b7c887916ae2"}, 214 | {file = "lmdb-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f683f3d9a1771f21a7788a9be98fae9f3ce13cb8d549d6074d0402f284572458"}, 215 | {file = "lmdb-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73332a830c72d76d57744cd2b29eca2c258bc406273ca4ee07dc9e48ae84d712"}, 216 | {file = "lmdb-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c178c5134e942256a830b0bca7bb052d3d7c645b4b8759d720ab49ec36b3aae"}, 217 | {file = "lmdb-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:12047c239ab6ccbbc9db99277aabcfe1c15b1cfc9ea33b92ab30ddd6f0823a10"}, 218 | {file = "lmdb-1.4.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:91930a2a7eb9acc4d687f9067d6f9ec83c9673bbee55823badbbee2f9a3e9970"}, 219 | {file = "lmdb-1.4.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:1b106eb7a23b6a224bc7dfe2bd5a34c84973dda039965ae99106e10d22833dd9"}, 220 | {file = "lmdb-1.4.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d7779ccfacd5f4c62f28485dd2427b54d19dd7016000e6237816a3750287a82"}, 221 | {file = "lmdb-1.4.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c1f1eff7ae8d8d534309f05e274fd646dd1d4abf5157c59db59a54a55463371"}, 222 | {file = "lmdb-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b6354df94d241e8c0158f716902224109a5f3f7ed9a24447a25f968427f61d77"}, 223 | {file = "lmdb-1.4.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:64cf7470edfc45ff0369956e40a0784b5225097569299b91f893bd50fa336f52"}, 224 | {file = "lmdb-1.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3c15d344731507fcfddb911a86d325e867c5574751af28591e82ecf21aad1e5"}, 225 | {file = "lmdb-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342550b86bb6275bfb89dbde9e48385da51d57124433bd464cd7681d0702f566"}, 226 | {file = "lmdb-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:3a99a3859427fbc273ae1e932b3e7da946089757e74a05a24a19f5c4a1aba933"}, 227 | {file = "lmdb-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f71da9bd33fd17c9cdbe2bd4ce87f4b36b8f044927df4220bec4b03f209c78a2"}, 228 | {file = "lmdb-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e9ff50ad20d890bc63524230237a61b6eb3be96ad6a6ac475e8ba1a1f2c751f"}, 229 | {file = "lmdb-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81abf9475a62b7ced1ac0352967106b7ed1ac5d1c1a0d23ed24abe55a28f9884"}, 230 | {file = "lmdb-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:c9fa31743b447a3fbbbdaefc858de1c761568d855155dec54d5ad490f88856b6"}, 231 | {file = "lmdb-1.4.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:26ef8fa7bd34a64f78f5e16fa9bcce0fe2ad682dd26ef078f95a8847dacb1171"}, 232 | {file = "lmdb-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f5dc8a335f7925fd667d62a5e43bed3aa35959b32b233fe0112a6ef02e07877"}, 233 | {file = "lmdb-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ba5d78b0ff130b38a56b7161ceb7e27ba4364d827d2bbb251c24b06c28c64cd"}, 234 | {file = "lmdb-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3b84f6a349ed1bd3fa4e6c3c6b711d0389cc8d9206733cb92feffaf102998e0c"}, 235 | {file = "lmdb-1.4.1-pp27-pypy_73-macosx_10_7_x86_64.whl", hash = "sha256:a428e6b0e298290b91b7d0ce409f595c2c9027d7f2076c39ba006290b90d14cc"}, 236 | {file = "lmdb-1.4.1-pp27-pypy_73-win_amd64.whl", hash = "sha256:885d3f3bf51b9167d368e37b1f1277eabf595dceefd69a489bd81c1ffd3d8ffd"}, 237 | {file = "lmdb-1.4.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4bd8e49d5209c652b2caa18a3a4c30524025d7868d34b7bb249c42f7997da240"}, 238 | {file = "lmdb-1.4.1.tar.gz", hash = "sha256:1f4c76af24e907593487c904ef5eba1993beb38ed385af82adb25a858f2d658d"}, 239 | ] 240 | 241 | [[package]] 242 | name = "markupsafe" 243 | version = "2.1.3" 244 | description = "Safely add untrusted strings to HTML/XML markup." 245 | optional = false 246 | python-versions = ">=3.7" 247 | files = [ 248 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, 249 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, 250 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, 251 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, 252 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, 253 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, 254 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, 255 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, 256 | {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, 257 | {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, 258 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, 259 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, 260 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, 261 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, 262 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, 263 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, 264 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, 265 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, 266 | {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, 267 | {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, 268 | {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, 269 | {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, 270 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, 271 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, 272 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, 273 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, 274 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, 275 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, 276 | {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, 277 | {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, 278 | {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, 279 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, 280 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, 281 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, 282 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, 283 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, 284 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, 285 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, 286 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, 287 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, 288 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, 289 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, 290 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, 291 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, 292 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, 293 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, 294 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, 295 | {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, 296 | {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, 297 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, 298 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, 299 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, 300 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, 301 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, 302 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, 303 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, 304 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, 305 | {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, 306 | {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, 307 | {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, 308 | ] 309 | 310 | [[package]] 311 | name = "packaging" 312 | version = "23.2" 313 | description = "Core utilities for Python packages" 314 | optional = false 315 | python-versions = ">=3.7" 316 | files = [ 317 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 318 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 319 | ] 320 | 321 | [[package]] 322 | name = "peewee" 323 | version = "3.17.0" 324 | description = "a little orm" 325 | optional = false 326 | python-versions = "*" 327 | files = [ 328 | {file = "peewee-3.17.0.tar.gz", hash = "sha256:3a56967f28a43ca7a4287f4803752aeeb1a57a08dee2e839b99868181dfb5df8"}, 329 | ] 330 | 331 | [[package]] 332 | name = "pygments" 333 | version = "2.17.2" 334 | description = "Pygments is a syntax highlighting package written in Python." 335 | optional = false 336 | python-versions = ">=3.7" 337 | files = [ 338 | {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, 339 | {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, 340 | ] 341 | 342 | [package.extras] 343 | plugins = ["importlib-metadata"] 344 | windows-terminal = ["colorama (>=0.4.6)"] 345 | 346 | [[package]] 347 | name = "pysmartdl" 348 | version = "1.3.4" 349 | description = "A Smart Download Manager for Python" 350 | optional = false 351 | python-versions = "*" 352 | files = [ 353 | {file = "pySmartDL-1.3.4-py3-none-any.whl", hash = "sha256:671c277ca710fb9b6603b19176f5c091041ec4ef6dcdb507c9a983a89ca35d31"}, 354 | {file = "pySmartDL-1.3.4.tar.gz", hash = "sha256:35275d1694f3474d33bdca93b27d3608265ffd42f5aeb28e56f38b906c0c35f4"}, 355 | ] 356 | 357 | [[package]] 358 | name = "pytz" 359 | version = "2023.3.post1" 360 | description = "World timezone definitions, modern and historical" 361 | optional = false 362 | python-versions = "*" 363 | files = [ 364 | {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, 365 | {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, 366 | ] 367 | 368 | [[package]] 369 | name = "requests" 370 | version = "2.32.0" 371 | description = "Python HTTP for Humans." 372 | optional = false 373 | python-versions = ">=3.8" 374 | files = [ 375 | {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, 376 | {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, 377 | ] 378 | 379 | [package.dependencies] 380 | certifi = ">=2017.4.17" 381 | charset-normalizer = ">=2,<4" 382 | idna = ">=2.5,<4" 383 | urllib3 = ">=1.21.1,<3" 384 | 385 | [package.extras] 386 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 387 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 388 | 389 | [[package]] 390 | name = "setuptools" 391 | version = "70.0.0" 392 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 393 | optional = false 394 | python-versions = ">=3.8" 395 | files = [ 396 | {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, 397 | {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, 398 | ] 399 | 400 | [package.extras] 401 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 402 | testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 403 | 404 | [[package]] 405 | name = "snowballstemmer" 406 | version = "2.2.0" 407 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 408 | optional = false 409 | python-versions = "*" 410 | files = [ 411 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, 412 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, 413 | ] 414 | 415 | [[package]] 416 | name = "sphinx" 417 | version = "2.4.5" 418 | description = "Python documentation generator" 419 | optional = false 420 | python-versions = ">=3.5" 421 | files = [ 422 | {file = "Sphinx-2.4.5-py3-none-any.whl", hash = "sha256:02d7e9dc5f30caa42a682b26de408b755a55c7b07f356a30a3b6300bf7d4740e"}, 423 | {file = "Sphinx-2.4.5.tar.gz", hash = "sha256:b00394e90463e7482c4cf59e7db1c8604baeca1468abfc062904dedc1cea6fcc"}, 424 | ] 425 | 426 | [package.dependencies] 427 | alabaster = ">=0.7,<0.8" 428 | babel = ">=1.3,<2.0 || >2.0" 429 | colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} 430 | docutils = ">=0.12,<0.18" 431 | imagesize = "*" 432 | Jinja2 = ">=2.3" 433 | packaging = "*" 434 | Pygments = ">=2.0" 435 | requests = ">=2.5.0" 436 | setuptools = "*" 437 | snowballstemmer = ">=1.1" 438 | sphinxcontrib-applehelp = "*" 439 | sphinxcontrib-devhelp = "*" 440 | sphinxcontrib-htmlhelp = "*" 441 | sphinxcontrib-jsmath = "*" 442 | sphinxcontrib-qthelp = "*" 443 | sphinxcontrib-serializinghtml = "*" 444 | 445 | [package.extras] 446 | docs = ["sphinxcontrib-websupport"] 447 | test = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-import-order", "html5lib", "mypy (>=0.761)", "pytest (<5.3.3)", "pytest-cov"] 448 | 449 | [[package]] 450 | name = "sphinx-autodoc-typehints" 451 | version = "1.10.3" 452 | description = "Type hints (PEP 484) support for the Sphinx autodoc extension" 453 | optional = false 454 | python-versions = ">=3.5.2" 455 | files = [ 456 | {file = "sphinx-autodoc-typehints-1.10.3.tar.gz", hash = "sha256:a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"}, 457 | {file = "sphinx_autodoc_typehints-1.10.3-py3-none-any.whl", hash = "sha256:27c9e6ef4f4451766ab8d08b2d8520933b97beb21c913f3df9ab2e59b56e6c6c"}, 458 | ] 459 | 460 | [package.dependencies] 461 | Sphinx = ">=2.1" 462 | 463 | [package.extras] 464 | test = ["dataclasses", "pytest (>=3.1.0)", "sphobjinv (>=2.0)", "typing-extensions (>=3.5)"] 465 | type-comments = ["typed-ast (>=1.4.0)"] 466 | 467 | [[package]] 468 | name = "sphinx-rtd-theme" 469 | version = "0.4.3" 470 | description = "Read the Docs theme for Sphinx" 471 | optional = false 472 | python-versions = "*" 473 | files = [ 474 | {file = "sphinx_rtd_theme-0.4.3-py2.py3-none-any.whl", hash = "sha256:00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4"}, 475 | {file = "sphinx_rtd_theme-0.4.3.tar.gz", hash = "sha256:728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"}, 476 | ] 477 | 478 | [package.dependencies] 479 | sphinx = "*" 480 | 481 | [[package]] 482 | name = "sphinxcontrib-applehelp" 483 | version = "1.0.4" 484 | description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" 485 | optional = false 486 | python-versions = ">=3.8" 487 | files = [ 488 | {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, 489 | {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, 490 | ] 491 | 492 | [package.extras] 493 | lint = ["docutils-stubs", "flake8", "mypy"] 494 | test = ["pytest"] 495 | 496 | [[package]] 497 | name = "sphinxcontrib-devhelp" 498 | version = "1.0.2" 499 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." 500 | optional = false 501 | python-versions = ">=3.5" 502 | files = [ 503 | {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, 504 | {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, 505 | ] 506 | 507 | [package.extras] 508 | lint = ["docutils-stubs", "flake8", "mypy"] 509 | test = ["pytest"] 510 | 511 | [[package]] 512 | name = "sphinxcontrib-htmlhelp" 513 | version = "2.0.1" 514 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" 515 | optional = false 516 | python-versions = ">=3.8" 517 | files = [ 518 | {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, 519 | {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, 520 | ] 521 | 522 | [package.extras] 523 | lint = ["docutils-stubs", "flake8", "mypy"] 524 | test = ["html5lib", "pytest"] 525 | 526 | [[package]] 527 | name = "sphinxcontrib-jsmath" 528 | version = "1.0.1" 529 | description = "A sphinx extension which renders display math in HTML via JavaScript" 530 | optional = false 531 | python-versions = ">=3.5" 532 | files = [ 533 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, 534 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, 535 | ] 536 | 537 | [package.extras] 538 | test = ["flake8", "mypy", "pytest"] 539 | 540 | [[package]] 541 | name = "sphinxcontrib-qthelp" 542 | version = "1.0.3" 543 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." 544 | optional = false 545 | python-versions = ">=3.5" 546 | files = [ 547 | {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, 548 | {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, 549 | ] 550 | 551 | [package.extras] 552 | lint = ["docutils-stubs", "flake8", "mypy"] 553 | test = ["pytest"] 554 | 555 | [[package]] 556 | name = "sphinxcontrib-serializinghtml" 557 | version = "1.1.5" 558 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." 559 | optional = false 560 | python-versions = ">=3.5" 561 | files = [ 562 | {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, 563 | {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, 564 | ] 565 | 566 | [package.extras] 567 | lint = ["docutils-stubs", "flake8", "mypy"] 568 | test = ["pytest"] 569 | 570 | [[package]] 571 | name = "tqdm" 572 | version = "4.66.1" 573 | description = "Fast, Extensible Progress Meter" 574 | optional = false 575 | python-versions = ">=3.7" 576 | files = [ 577 | {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, 578 | {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, 579 | ] 580 | 581 | [package.dependencies] 582 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 583 | 584 | [package.extras] 585 | dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] 586 | notebook = ["ipywidgets (>=6)"] 587 | slack = ["slack-sdk"] 588 | telegram = ["requests"] 589 | 590 | [[package]] 591 | name = "urllib3" 592 | version = "2.2.2" 593 | description = "HTTP library with thread-safe connection pooling, file post, and more." 594 | optional = false 595 | python-versions = ">=3.8" 596 | files = [ 597 | {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, 598 | {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, 599 | ] 600 | 601 | [package.extras] 602 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 603 | h2 = ["h2 (>=4,<5)"] 604 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 605 | zstd = ["zstandard (>=0.18.0)"] 606 | 607 | [metadata] 608 | lock-version = "2.0" 609 | python-versions = "^3.8" 610 | content-hash = "dca740debd3d360d5a093b18871ce9cfbcc40c4ff40e1ff1193d9c8976bbd1cf" 611 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "conceptnet-lite" 3 | version = "0.2.0" 4 | description = "Python library to work with ConceptNet offline without the need of PostgreSQL" 5 | authors = ["Roman Inflianskas "] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/ldtoolkit/conceptnet-lite" 9 | classifiers = ["Development Status :: 7 - Inactive"] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | lmdb = "^1.0" 14 | peewee = "^3.10" 15 | tqdm = "^4.35" 16 | pysmartdl = "^1.3" 17 | 18 | [tool.poetry.dev-dependencies] 19 | sphinx = "^2.2" 20 | sphinx-rtd-theme = "^0.4.3" 21 | sphinx-autodoc-typehints = "^1.7" 22 | 23 | [build-system] 24 | requires = ["poetry>=0.12"] 25 | build-backend = "poetry.masonry.api" 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from distutils.core import setup 3 | 4 | packages = \ 5 | ['conceptnet_lite'] 6 | 7 | package_data = \ 8 | {'': ['*']} 9 | 10 | install_requires = \ 11 | ['lmdb>=0.97.0,<0.98.0', 12 | 'peewee>=3.10,<4.0', 13 | 'pysmartdl>=1.3,<2.0', 14 | 'tqdm>=4.35,<5.0'] 15 | 16 | setup_kwargs = { 17 | 'name': 'conceptnet-lite', 18 | 'version': '0.1.27', 19 | 'description': 'Python library to work with ConceptNet offline without the need of PostgreSQL', 20 | 'long_description': '# conceptnet-lite\n[![License](https://img.shields.io/pypi/l/conceptnet-lite.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/conceptnet-lite.svg)\n[![PyPI](https://img.shields.io/pypi/v/conceptnet-lite.svg)](https://pypi.org/project/conceptnet-lite/)\n[![Documentation Status](https://img.shields.io/readthedocs/conceptnet-lite.svg)](http://conceptnet-lite.readthedocs.io/en/latest/)\n\nConceptnet-lite is a Python library for working with ConceptNet offline without the need for PostgreSQL.\n\nThe library comes with Apache License 2.0, and is separate from ConceptNet itself. The ConceptNet is available under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/) license, which also applies to the formatted database file that we provide. See [here](https://github.com/commonsense/conceptnet5/wiki/Copying-and-sharing-ConceptNet) for the list of conditions for using ConceptNet data.\n\nThis is the official citation for ConceptNet if you use it in research:\n\n> Robyn Speer, Joshua Chin, and Catherine Havasi. 2017. "ConceptNet 5.5: An Open Multilingual Graph of General Knowledge." In proceedings of AAAI 31.\n\n## Installation\n\nTo install `conceptnet-lite` use `pip`:\n\n```shell\n$ pip install conceptnet-lite\n```\n\n## Connecting to the database\n\nBefore you can use `conceptnet-lite`, you will need to obtain ConceptNet dabase file. You have two options: download pre-made one or build it yourself from the raw ConceptNet assertions file.\n\n### Downloading the ConceptNet database \n\nConceptNet releases happen once a year. You can use `conceptnet-lite` to build your own database from the raw assertions file (see below), but if there is a pre-built file it will be faster to just get that one. `conceptnet-lite` can download and unpack it to the specified folder automatically.\n\nHere is a [link](https://conceptnet-lite.fra1.cdn.digitaloceanspaces.com/conceptnet.db.zip) to a compressed database for ConceptNet 5.7. This link is used automatically if you do not supply the alternative.\n\n```python\nimport conceptnet_lite\n\nconceptnet_lite.connect("/path/to/conceptnet.db")\n```\n\nThis command both downloads the resource (our build for ConceptNet 5.7) and connects to the database. If path specified as the first argument does not exist, it will be created (unless there is a permissions problem). Note that the database file is quite large (over 9 Gb). \n\nIf your internet connection is intermittent, the built-in download function may give you errors. If so, just download the file separately, unpack it to the directory of your choice and provide the path to the `.connect()` method as described below.\n\n### Building the database for a new release.\n\nIf a database file is not found in the folder specified in the `db_path` argument, `conceptnet-lite` will attempt to automatically download the raw assertions file from [here](https://github.com/commonsense/conceptnet5/wiki/Downloads) and build the database. This takes a couple of hours, so we recommend getting the pre-built file.\n\nIf you provide a path, this is where the database will be built. Note that the database file is quite large (over 9 Gb). Note that you need to pass `db_download_url=None` to force the library build the database from dump.\n\n```python\nimport conceptnet_lite\n\nconceptnet_lite.connect("/path/to/conceptnet.db", db_download_url=None)\n```\n\nIf the specified does not exist, it will be created (unless there is a permissions problem). If no path is specified, and no database file is not found in the current working directory, `conceptnet-lite` will attempt to build one in the current working directory. \n\nOnce the database is built, `conceptnet-lite` will connect to it automatically.\n\n### Loading the ConceptNet database \n\nOnce you have the database file, all you need to do is to pass the path to it to the `.connect()` method.\n\n```python\nimport conceptnet_lite\n\nconceptnet_lite.connect("/path/to/conceptnet.db")\n```\n\nIf no path is specified, `conceptnet-lite` will check if a database file exists in the current working directory. If it is not found, it will trigger the process of downloading the pre-built database (see above).\n\n## Accessing concepts\n\nConcepts objects are created by looking for every entry that matches the input string exactly.\nIf none is found, the `peewee.DoesNotExist` exception will be raised.\n\n```python\nfrom conceptnet_lite import Label\n\ncat_concepts = Label.get(text=\'cat\').concepts \nfor c in cat_concepts:\n print(" Concept URI:", c.uri)\n print(" Concept text:", c.text)\n```\n```console\nConcept URI: /c/en/cat\nConcept text: cat\nConcept URI: /c/en/cat/n\nConcept text: cat\nConcept URI: /c/en/cat/n/wn/animal\nConcept text: cat\nConcept URI: /c/en/cat/n/wn/person\n...\n```\n\n`concept.uri` provides access to ConceptNet URIs, as described [here](https://github.com/commonsense/conceptnet5/wiki/URI-hierarchy). You can also retrieve only the text of the entry by `concept.text`.\n\n## Working with languages\n\nYou can limit the languages to search for matches. Label.get() takes an optional `language` attribute that is expected to be an instance `Language`, which in turn is created by calling `Language.get()` with `name` argument.\nList of available languages and their codes are described [here](https://github.com/commonsense/conceptnet5/wiki/Languages).\n\n```python\nfrom conceptnet_lite import Label\n\ncat_concepts = Label.get(text=\'cat\', language=\'en\').concepts \nfor c in cat_concepts:\n print(" Concept URI:", c.uri)\n print(" Concept text:", c.text)\n print(" Concept language:", c.language.name)\n```\n\n```console\n Concept URI: /c/en/cat\n Concept text: cat\n Concept language: en\n Concept URI: /c/en/cat/n\n Concept text: cat\n Concept language: en\n Concept URI: /c/en/cat/n/wn/animal\n Concept text: cat\n Concept language: en\n Concept URI: /c/en/cat/n/wn/person\n Concept text: cat\n Concept language: en\n...\n```\n\n\n## Querying edges between concepts\n\nTo retrieve the set of relations between two concepts, you need to create the concept objects (optionally specifying the language as described above). `cn.edges_between()` method retrieves all edges between the specified concepts. You can access its URI and a number of attributes, as shown below.\n\nSome ConceptNet relations are symmetrical: for example, the antonymy between *white* and *black* works both ways. Some relations are asymmetrical: e.g. the relation between *cat* and *mammal* is either hyponymy or hyperonymy, depending on the direction. The `two_way` argument lets you choose whether the query should be symmetrical or not.\n\n```python\nfrom conceptnet_lite import Label, edges_between\n\nintrovert_concepts = Label.get(text=\'introvert\', language=\'en\').concepts\nextrovert_concepts = Label.get(text=\'extrovert\', language=\'en\').concepts\nfor e in edges_between(introvert_concepts, extrovert_concepts, two_way=False):\n print(" Edge URI:", e.uri)\n print(" Edge name:", e.relation.name)\n print(" Edge start node:", e.start.text)\n print(" Edge end node:", e.end.text)\n print(" Edge metadata:", e.etc)\n```\n```console\n Edge URI: /a/[/r/antonym/,/c/en/introvert/n/,/c/en/extrovert/]\n Edge name: antonym\n Edge start node: introvert\n Edge end node: extrovert\n Edge metadata: {\'dataset\': \'/d/wiktionary/en\', \'license\': \'cc:by-sa/4.0\', \'sources\': [{\'contributor\': \'/s/resource/wiktionary/en\', \'process\': \'/s/process/wikiparsec/2\'}, {\'contributor\': \'/s/resource/wiktionary/fr\', \'process\': \'/s/process/wikiparsec/2\'}], \'weight\': 2.0}\n```\n\n* **e.relation.name**: the name of ConceptNet relation. Full list [here](https://github.com/commonsense/conceptnet5/wiki/Relations).\n\n* **e.start.text, e.end.text**: the source and the target concepts in the edge\n\n* **e.etc**: the ConceptNet [metadata](https://github.com/commonsense/conceptnet5/wiki/Edges) dictionary contains the source dataset, sources, weight, and license. For example, the introvert:extrovert edge for English contains the following metadata:\n\n```json\n{\n\t"dataset": "/d/wiktionary/en",\n\t"license": "cc:by-sa/4.0",\n\t"sources": [{\n\t\t"contributor": "/s/resource/wiktionary/en",\n\t\t"process": "/s/process/wikiparsec/2"\n\t}, {\n\t\t"contributor": "/s/resource/wiktionary/fr",\n\t\t"process": "/s/process/wikiparsec/2"\n\t}],\n\t"weight": 2.0\n}\n```\n\n## Accessing all relations for a given concepts\n\nYou can also retrieve all relations between a given concepts and all other concepts, with the same options as above:\n\n```python\nfrom conceptnet_lite import Label, edges_for\n\nfor e in edges_for(Label.get(text=\'introvert\', language=\'en\').concepts, same_language=True):\n print(e.start.text, "::", e.end.text, "|", e.relation.name)\n```\n```console\nextrovert :: introvert | antonym\nintrovert :: extrovert | antonym\noutrovert :: introvert | antonym\nreflection :: introvert | at_location\nintroverse :: introvert | derived_from\nintroversible :: introvert | derived_from\nintroversion :: introvert | derived_from\nintroversion :: introvert | derived_from\nintroversive :: introvert | derived_from\nintroverted :: introvert | derived_from\n...\n```\n\nThe same set of edge attributes are available for `edges_between` and `edges_for` (e.uri, e.relation.name, e.start.text, e.end.text, e.etc).\n\nNote that we have used optional argument `same_language=True`. By supplying this argument we make `edges_for` return\nrelations, both ends of which are in the same language. If this argument is skipped it is possible to get edges to\nconcepts in languages other than the source concepts language. For example, the same command as above with `same_language=False` will include the following in the output:\n\n```console\nkääntyä_sisäänpäin :: introvert | synonym\nsulkeutua :: introvert | synonym\nsulkeutunut :: introvert | synonym\nintroverti :: introvert | synonym\nasociale :: introvert | synonym\nintroverso :: introvert | synonym\nintrovertito :: introvert | synonym\n内向 :: introvert | synonym\n```\n\n## Accessing concept edges with a given relation direction\n\nYou can also query the relations that have a specific concept as target or source. This is achieved with `concept.edges_out` and `concept.edges_in`, as follows:\n\n```python\nfrom conceptnet_lite import Label\n\nconcepts = Label.get(text=\'introvert\', language=\'en\').concepts \nfor c in concepts:\n print(" Concept text:", c.text)\n if c.edges_out:\n print(" Edges out:")\n for e in c.edges_out:\n print(" Edge URI:", e.uri)\n print(" Relation:", e.relation.name)\n print(" End:", e.end.text)\n if c.edges_in:\n print(" Edges in:")\n for e in c.edges_in:\n print(" Edge URI:", e.uri)\n print(" Relation:", e.relation.name)\n print(" End:", e.end.text)\n```\n```console\n Concept text: introvert\n Edges out:\n Edge URI: /a/[/r/etymologically_derived_from/,/c/en/introvert/,/c/la/introvertere/]\n Relation: etymologically_derived_from\n End: introvertere\n...\n Edges in:\n Edge URI: /a/[/r/antonym/,/c/cs/extrovert/n/,/c/en/introvert/]\n Relation: antonym\n End: introvert\n...\n```\n\n## Traversing all the data for a language\n\nYou can go over all concepts for a given language. For illustration, let us try Old Norse, a "small" language with the code "non" and vocab size of 7868, according to the [ConceptNet language statistics](https://github.com/commonsense/conceptnet5/wiki/Languages).\n\n```python\nfrom conceptnet_lite import Language\n\nmylanguage = Language.get(name=\'non\')\nfor l in mylanguage.labels:\n print(" Label:", l.text)\n for c in l.concepts:\n print(" Concept URI:", c.uri)\n if c.edges_out:\n print(" Edges out:")\n for e in c.edges_out:\n print(" Edge URI:", e.uri)\n if c.edges_in:\n print(" Edges in:")\n for e in c.edges_in:\n print(" Edge URI:", e.uri)\n```\n```console\n Label: andsœlis\n Concept URI: /c/non/andsœlis/r\n Edges out:\n Edge URI: /a/[/r/antonym/,/c/non/andsœlis/r/,/c/non/réttsœlis/]\n Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/against/]\n Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/course/]\n Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/sun/]\n Edge URI: /a/[/r/related_to/,/c/non/andsœlis/r/,/c/en/widdershins/]\n Edge URI: /a/[/r/synonym/,/c/non/andsœlis/r/,/c/non/rangsœlis/]\n Concept URI: /c/non/andsœlis\n Edges out:\n Edge URI: /a/[/r/external_url/,/c/non/andsœlis/,/c/en.wiktionary.org/wiki/andsœlis/]\n Label: réttsœlis\n Concept URI: /c/non/réttsœlis\n Edges in:\n Edge URI: /a/[/r/antonym/,/c/non/andsœlis/r/,/c/non/réttsœlis/]\n...\n```\n\n## Accessing Concepts by URI\n\nYou can access concept ORM objects directly by providing a desired ConceptNet URI. This is done as follows:\n\n```python\nfrom conceptnet_lite import Concept\n\nedge_object = Edge.get(start=\'/c/en/example\')\nconcept_object = Concept.get(uri=\'/c/en/example\')\n```\n', 21 | 'author': 'Roman Inflianskas', 22 | 'author_email': 'infroma@gmail.com', 23 | 'url': 'https://github.com/ldtoolkit/conceptnet-lite', 24 | 'packages': packages, 25 | 'package_data': package_data, 26 | 'install_requires': install_requires, 27 | 'python_requires': '>=3.6,<4.0', 28 | } 29 | 30 | 31 | setup(**setup_kwargs) 32 | --------------------------------------------------------------------------------