├── .github ├── FUNDING.yml └── workflows │ ├── python-package.yml │ └── python-publish.yml ├── LICENSE ├── README.md ├── duneanalytics ├── __init__.py ├── __version__.py └── duneanalytics.py ├── sample ├── 1inch.svg ├── balancer.svg └── bancor.svg ├── setup.py └── tests ├── __init__.py ├── conftest.py └── test_duneanalytics.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [itzmestar] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: [3.9] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install flake8 pytest requests>=2.18.4 31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with pytest 39 | env: 40 | DUNE_USER: ${{ secrets.DUNE_USER }} 41 | DUNE_PASS: ${{ secrets.DUNE_PASS }} 42 | 43 | run: | 44 | pytest 45 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /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 | # Dune Analytics 2 | 3 | [![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-390/) 4 | 5 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 6 | 7 | [![Build](https://github.com/itzmestar/duneanalytics/actions/workflows/python-package.yml/badge.svg)](https://github.com/itzmestar/duneanalytics/actions/workflows/python-package.yml) 8 |
9 | 10 | ### Unofficial Python Library for [Dune Analytics](https://duneanalytics.com/) 11 | 12 | The library can be used to fetch the table data from `python` backend. 13 | 14 | #### Disclaimer: Use at your own risk! 15 | It may not work for some/all urls. 16 | 17 | This library doesn't run the query, rather it fetches the query result from the backend. 18 | 19 |
20 | 21 | ### Charts Plotted: 22 | Here are some examples of charts plotted in Googlesheet after fetching the data. 23 | 24 | ----- 25 | 26 | [**@balancerlabs / Balancer New/Old Traders**](https://duneanalytics.com/queries/31203/62900) 27 | 28 | ![Balancer](sample/balancer.svg) 29 | 30 | ----- 31 | 32 | [**@k06a / 1inch New/Old Users Per Day**](https://duneanalytics.com/queries/1193/2032) 33 | 34 | ![1inch](sample/1inch.svg) 35 | 36 | ----- 37 | 38 | [**@Bancor / Bancor Unique Protected Wallets Over Time**](https://duneanalytics.com/queries/12948/25894) 39 | 40 | ![Bancor](sample/bancor.svg) 41 | 42 |
43 | 44 | ### Installation: 45 | 46 | use pip to install: 47 | 48 | ``` 49 | pip install duneanalytics 50 | ``` 51 | 52 |
53 | 54 | ### Authentication: 55 | 56 | You need to have `username` & `password` for [Dune Analytics](https://duneanalytics.com/) 57 | 58 |
59 | 60 | ### Example usage: 61 | 62 | ``` 63 | from duneanalytics import DuneAnalytics 64 | 65 | # initialize client 66 | dune = DuneAnalytics('username', 'password') 67 | 68 | # try to login 69 | dune.login() 70 | 71 | # fetch token 72 | dune.fetch_auth_token() 73 | 74 | # fetch query result id using query id 75 | # query id for any query can be found from the url of the query: 76 | # for example: 77 | # https://dune.com/queries/4494/8769 => 4494 78 | # https://dune.com/queries/3705/7192 => 3705 79 | # https://dune.com/queries/3751/7276 => 3751 80 | 81 | result_id = dune.query_result_id(query_id=5508) 82 | 83 | # fetch query result 84 | data = dune.query_result(result_id) 85 | 86 | For some queries above might not work specially if using v3. Try to use below in that case: 87 | # for example: 88 | # https://dune.com/queries/1105079 89 | 90 | result_id = dune.query_result_id_v3(query_id=1105079) 91 | 92 | # fetch execution result 93 | data = dune.get_execution_result(result_id) 94 | 95 | ``` 96 | -------------------------------------------------------------------------------- /duneanalytics/__init__.py: -------------------------------------------------------------------------------- 1 | from .__version__ import __title__, __description__, __url__ 2 | from .__version__ import __version__, __build__, __author_email__ 3 | from .__version__ import __author__, __license__, __copyright__ 4 | 5 | from .duneanalytics import DuneAnalytics 6 | -------------------------------------------------------------------------------- /duneanalytics/__version__.py: -------------------------------------------------------------------------------- 1 | 2 | __title__ = 'duneanalytics' 3 | __description__ = 'Unofficial library for Dune Analytics.' 4 | __url__ = 'https://github.com/itzmestar/duneanalytics' 5 | __version__ = '2.0.0' 6 | __build__ = 0x010001 7 | __author__ = 'Tarique Anwer' 8 | __author_email__ = 'itzmetariq@gmail.com' 9 | __license__ = 'Apache License 2.0' 10 | __copyright__ = 'Copyright 2021 Tarique Anwer' 11 | -------------------------------------------------------------------------------- /duneanalytics/duneanalytics.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- # 2 | """This provides the DuneAnalytics class implementation""" 3 | 4 | from requests import Session 5 | import logging 6 | 7 | # --------- Constants --------- # 8 | 9 | BASE_URL = "https://dune.com" 10 | GRAPH_URL = 'https://core-hsr.dune.com/v1/graphql' 11 | GRAPH_URL_NEW = 'https://app-api.dune.com/v1/graphql' 12 | 13 | # --------- Constants --------- # 14 | logging.basicConfig( 15 | level=logging.INFO, 16 | format='%(asctime)s : %(levelname)s : %(funcName)-9s : %(message)s' 17 | ) 18 | logger = logging.getLogger("dune") 19 | 20 | 21 | class DuneAnalytics: 22 | """ 23 | DuneAnalytics class to act as python client for duneanalytics.com. 24 | All requests to be made through this class. 25 | """ 26 | 27 | def __init__(self, username, password): 28 | """ 29 | Initialize the object 30 | :param username: username for duneanalytics.com 31 | :param password: password for duneanalytics.com 32 | """ 33 | self.csrf = None 34 | self.auth_refresh = None 35 | self.token = None 36 | self.username = username 37 | self.password = password 38 | self.query_id = None 39 | self.session = Session() 40 | headers = { 41 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,' 42 | 'image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 43 | 'dnt': '1', 44 | 'sec-ch-ua': '"Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"', 45 | 'sec-ch-ua-mobile': '?0', 46 | 'sec-fetch-dest': 'document', 47 | 'sec-fetch-mode': 'cors', 48 | 'sec-fetch-site': 'same-site', 49 | 'origin': BASE_URL, 50 | 'upgrade-insecure-requests': '1' 51 | } 52 | self.session.headers.update(headers) 53 | 54 | def login(self): 55 | """ 56 | Try to login to duneanalytics.com & get the token 57 | :return: 58 | """ 59 | login_url = BASE_URL + '/auth/login' 60 | csrf_url = BASE_URL + '/api/auth/csrf' 61 | auth_url = BASE_URL + '/api/auth' 62 | 63 | # fetch login page 64 | self.session.get(login_url) 65 | 66 | # get csrf token 67 | self.session.post(csrf_url) 68 | self.csrf = self.session.cookies.get('csrf') 69 | 70 | # try to login 71 | form_data = { 72 | 'action': 'login', 73 | 'username': self.username, 74 | 'password': self.password, 75 | 'csrf': self.csrf, 76 | 'next': BASE_URL 77 | } 78 | 79 | self.session.post(auth_url, data=form_data) 80 | self.auth_refresh = self.session.cookies.get('auth-refresh') 81 | if self.auth_refresh is None: 82 | logger.warning("Login Failed!") 83 | 84 | def fetch_auth_token(self): 85 | """ 86 | Fetch authorization token for the user 87 | :return: 88 | """ 89 | session_url = BASE_URL + '/api/auth/session' 90 | 91 | response = self.session.post(session_url) 92 | if response.status_code == 200: 93 | self.token = response.json().get('token') 94 | if self.token is None: 95 | logger.warning("Fetching Token Failed!") 96 | else: 97 | logger.error(response.text) 98 | 99 | def query_result_id(self, query_id): 100 | """ 101 | Fetch the query result id for a query 102 | 103 | :param query_id: provide the query_id 104 | :return: 105 | """ 106 | query_data = {"operationName": "GetResult", "variables": {"query_id": query_id}, 107 | "query": "query GetResult($query_id: Int!, $parameters: [Parameter!]) " 108 | "{\n get_result_v2(query_id: $query_id, parameters: $parameters) " 109 | "{\n job_id\n result_id\n error_id\n __typename\n }\n}\n" 110 | } 111 | 112 | self.session.headers.update({'authorization': f'Bearer {self.token}'}) 113 | 114 | response = self.session.post(GRAPH_URL, json=query_data) 115 | if response.status_code == 200: 116 | data = response.json() 117 | logger.debug(data) 118 | if 'errors' in data: 119 | logger.error(data.get('errors')) 120 | return None 121 | result_id = data.get('data').get('get_result_v2').get('result_id') 122 | return result_id 123 | else: 124 | logger.error(response.text) 125 | return None 126 | 127 | def query_result_id_v3(self, query_id): 128 | """ 129 | Fetch the query result id for a query 130 | 131 | :param query_id: provide the query_id 132 | :return: 133 | """ 134 | self.query_id = query_id 135 | query_data = {"operationName": "GetResult", "variables": {"query_id": query_id, "parameters": []}, 136 | "query": "query GetResult($query_id: Int!, $parameters: [Parameter!]!) " 137 | "{\n get_result_v3(query_id: $query_id, parameters: $parameters) " 138 | "{\n job_id\n result_id\n error_id\n __typename\n }\n}\n" 139 | } 140 | 141 | self.session.headers.update({'authorization': f'Bearer {self.token}'}) 142 | 143 | response = self.session.post(GRAPH_URL, json=query_data) 144 | if response.status_code == 200: 145 | data = response.json() 146 | logger.debug(data) 147 | if 'errors' in data: 148 | logger.error(data.get('errors')) 149 | return None 150 | result_id = data.get('data').get('get_result_v3').get('result_id') 151 | return result_id 152 | else: 153 | logger.error(response.text) 154 | return None 155 | 156 | def query_result(self, result_id): 157 | """ 158 | Fetch the result for a query 159 | :param result_id: result id of the query 160 | :return: 161 | """ 162 | query_data = {"operationName": "FindResultDataByResult", 163 | "variables": {"result_id": result_id, "error_id": "00000000-0000-0000-0000-000000000000"}, 164 | "query": "query FindResultDataByResult($result_id: uuid!, $error_id: uuid!) " 165 | "{\n query_results(where: {id: {_eq: $result_id}}) " 166 | "{\n id\n job_id\n runtime\n generated_at\n columns\n __typename\n }" 167 | "\n query_errors(where: {id: {_eq: $error_id}}) {\n id\n job_id\n runtime\n" 168 | " message\n metadata\n type\n generated_at\n __typename\n }\n" 169 | "\n get_result_by_result_id(args: {want_result_id: $result_id}) {\n data\n __typename\n }\n}\n" 170 | } 171 | 172 | self.session.headers.update({'authorization': f'Bearer {self.token}'}) 173 | 174 | response = self.session.post(GRAPH_URL, json=query_data) 175 | if response.status_code == 200: 176 | data = response.json() 177 | logger.debug(data) 178 | return data 179 | else: 180 | logger.error(response.text) 181 | return {} 182 | 183 | def get_execution_result(self, execution_id): 184 | query_data = {"operationName": "GetExecution", 185 | "variables": {"execution_id": execution_id, "query_id": self.query_id, "parameters": []}, 186 | "query": "query GetExecution($execution_id: String!, $query_id: Int!, $parameters: [Parameter!]!)" 187 | " {\n get_execution(\n execution_id: $execution_id\n query_id: $query_id\n " 188 | "parameters: $parameters\n ) {\n execution_queued {\n execution_id\n " 189 | "execution_user_id\n position\n execution_type\n created_at\n " 190 | "__typename\n }\n execution_running {\n execution_id\n " 191 | "execution_user_id\n execution_type\n started_at\n created_at\n " 192 | "__typename\n }\n execution_succeeded {\n execution_id\n " 193 | "runtime_seconds\n generated_at\n columns\n data\n __typename\n }" 194 | "\n execution_failed {\n execution_id\n type\n message\n metadata" 195 | " {\n line\n column\n hint\n __typename\n }\n " 196 | "runtime_seconds\n generated_at\n __typename\n }\n __typename\n }\n}\n"} 197 | self.session.headers.update({'authorization': f'Bearer {self.token}'}) 198 | 199 | response = self.session.post(GRAPH_URL_NEW, json=query_data) 200 | if response.status_code == 200: 201 | data = response.json() 202 | logger.debug(data) 203 | return data 204 | else: 205 | logger.error(response.text) 206 | return {} 207 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | import os 3 | 4 | here = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | packages = ['duneanalytics'] 7 | 8 | requires = [ 9 | 'requests>=2.18.4', 10 | ] 11 | 12 | about = {} 13 | 14 | with open(os.path.join(here, 'duneanalytics', '__version__.py'), mode='r', encoding='utf-8') as f: 15 | exec(f.read(), about) 16 | 17 | with open('README.md', mode='r', encoding='utf-8') as f: 18 | readme = f.read() 19 | 20 | setuptools.setup( 21 | name=about['__title__'], 22 | version=about['__version__'], 23 | description=about['__description__'], 24 | long_description=readme, 25 | long_description_content_type='text/markdown', 26 | author=about['__author__'], 27 | author_email=about['__author_email__'], 28 | url=about['__url__'], 29 | packages=packages, 30 | install_requires=requires, 31 | license=about['__license__'], 32 | classifiers=[ 33 | "Programming Language :: Python :: 3", 34 | "Programming Language :: Python :: 3.6", 35 | "Programming Language :: Python :: 3.7", 36 | "Programming Language :: Python :: 3.8", 37 | "Programming Language :: Python :: 3.9", 38 | "License :: OSI Approved :: Apache Software License", 39 | "Operating System :: OS Independent", 40 | ], 41 | ) 42 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itzmestar/duneanalytics/1fff1548f39ee2645e8325efcd28ab59966c52d3/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | from duneanalytics import DuneAnalytics 4 | 5 | 6 | @pytest.fixture(scope='session') 7 | def dune(): 8 | print('===============Start=======================') 9 | yield DuneAnalytics(username=os.getenv('DUNE_USER'), password=os.getenv('DUNE_PASS')) 10 | print('===============End=======================') 11 | -------------------------------------------------------------------------------- /tests/test_duneanalytics.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.usefixtures('dune') 5 | class TestDuneAnalytics: 6 | """ 7 | Class to test DuneAnalytics 8 | """ 9 | def test_login(self, dune): 10 | # try to login 11 | dune.login() 12 | assert dune.auth_refresh is not None 13 | assert dune.username is not None 14 | assert dune.password is not None 15 | assert dune.token is None 16 | 17 | def test_fetch_auth_token(self, dune): 18 | # fetch authentication token 19 | # dune.login() 20 | dune.fetch_auth_token() 21 | assert dune.auth_refresh is not None 22 | assert dune.username is not None 23 | assert dune.password is not None 24 | assert dune.token is not None 25 | 26 | @pytest.mark.skip(reason='To be implemented') 27 | def test_query_result_id(self, dune): 28 | result_id = dune.query_result_id(query_id=3705) 29 | assert result_id is not None 30 | 31 | @pytest.mark.skip(reason='To be implemented') 32 | def test_query_result(self, dune): 33 | result_id = dune.query_result_id(query_id=3705) 34 | assert result_id is not None 35 | data = dune.query_result(result_id) 36 | assert data is not None 37 | 38 | def test_get_execution_result(self, dune): 39 | result_id = dune.query_result_id_v3(query_id=1105079) 40 | assert result_id is not None 41 | data = dune.get_execution_result(result_id) 42 | assert data is not None 43 | assert data.get('data', {}).get('get_execution') is not None 44 | assert data.get('data', {}).get('get_execution', {}).get('execution_succeeded') is not None 45 | --------------------------------------------------------------------------------