├── .env.example ├── .github └── workflows │ └── pytest.yml ├── .gitignore ├── .readthedocs.yaml ├── HISTORY.rst ├── LICENSE ├── Makefile ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── neon_api ├── __init__.py ├── __version__.py ├── client.py ├── exceptions.py ├── schema.py └── utils.py ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── setup.py ├── tests ├── cassettes │ └── test_integration │ │ ├── test_api_keys.yaml │ │ ├── test_api_keys_crud.yaml │ │ ├── test_branches.yaml │ │ ├── test_create_project.yaml │ │ ├── test_database.yaml │ │ ├── test_endpoints.yaml │ │ ├── test_get_projects.yaml │ │ ├── test_me.yaml │ │ ├── test_operations.yaml │ │ ├── test_project.yaml │ │ ├── test_project_delete.yaml │ │ └── test_roles.yaml ├── conftest.py └── test_integration.py └── v2.json /.env.example: -------------------------------------------------------------------------------- 1 | NEON_API_KEY=... 2 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ["3.9", "3.10", "3.11", "3.12"] 15 | 16 | steps: 17 | - name: Harden the runner (Audit all outbound calls) 18 | uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 19 | with: 20 | egress-policy: audit 21 | 22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: pip install -r requirements.txt 29 | # You can test your matrix by printing the current Python version 30 | - name: Run the tests with pytest 31 | run: make ci 32 | env: 33 | NEON_API_KEY: ${{ secrets.NEON_API_KEY }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | **.egg-info/** 3 | .coverage 4 | docs/_build/** 5 | build/** 6 | dist/** 7 | Pipfile 8 | Pipfile.lock 9 | .env 10 | venv/ 11 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.12" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | 22 | # Optionally build your docs in additional formats such as PDF and ePub 23 | # formats: 24 | # - pdf 25 | # - epub 26 | 27 | # Optional but recommended, declare the Python requirements required 28 | # to build your documentation 29 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 30 | python: 31 | install: 32 | - requirements: requirements.txt 33 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | 4 | 0.3.0 5 | 6 | - Bumps the Python data types used for the SDK 7 | 8 | 0.2.0 9 | ----- 10 | 11 | - Bumps the OpenAPI schema used for the SDK 12 | 13 | 0.1.6 14 | ----- 15 | 16 | - Fixes database update HTTP method 17 | 18 | 0.1.4 19 | ----- 20 | 21 | - Added connection_uri GET request 22 | 23 | 0.1.3 24 | ----- 25 | 26 | - Added poetry tooling support. 27 | 28 | 0.1.2 29 | ----- 30 | 31 | - Added support for Python 3.9. 32 | 33 | 34 | 0.1.1 35 | ----- 36 | 37 | - Packaging and README fixes. 38 | 39 | 40 | 0.1.0 41 | ----- 42 | 43 | - First release on PyPI. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | set -a && source .env && set +a; pytest --record-mode=none tests/ 3 | 4 | fmt: 5 | ruff format . 6 | 7 | ci: 8 | pytest --cov=neon_client --record-mode=none tests/ 9 | 10 | record: 11 | pytest --record-mode=rewrite tests/ 12 | 13 | schema: fetch-v2-schema 14 | datamodel-codegen \ 15 | --input v2.json \ 16 | --collapse-root-models \ 17 | --output neon_client/schema.py \ 18 | --additional-imports datetime.datetime,pydantic.dataclasses.dataclass \ 19 | --use-standard-collections \ 20 | --output-model-type dataclasses.dataclass \ 21 | # --input-file-type openapi \ 22 | --use-standard-collections \ 23 | --use-union-operator \ 24 | --target-python-version 3.11 \ 25 | --use-schema-description \ 26 | --snake-case-field \ 27 | --enable-version-header \ 28 | --use-double-quotes \ 29 | --allow-population-by-field-name \ 30 | --use-title-as-name \ 31 | --reuse-model \ 32 | --collapse-root-models \ 33 | # --field-constraints \ 34 | --disable-appending-item-suffix \ 35 | --allow-extra-fields \ 36 | --capitalise-enum-members \ 37 | --allow-extra-fields \ 38 | --use-field-description \ 39 | --use-default \ 40 | --use-enum-values \ 41 | --reuse-model \ 42 | --use-unique-items-as-set \ 43 | --set-default-enum-member \ 44 | --enum-field-as-literal one \ 45 | --allow-extra-fields \ 46 | --openapi-scopes {schemas,paths,tags,parameters} \ 47 | --use-operation-id-as-name \ 48 | --strict-nullable \ 49 | --keep-model-order \ 50 | --field-constraints \ 51 | 52 | # --use-annotated \ 53 | 54 | 55 | 56 | fetch-v2-schema: 57 | curl -O https://neon.tech/api_spec/release/v2.json 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `neon_api`: Python API wrapper for the [Neon API](https://api-docs.neon.tech/reference/getting-started-with-neon-api). 2 | 3 | `neon_api` is a Python wrapper designed to simplify interactions with the [Neon API](https://api-docs.neon.tech/reference/getting-started-with-neon-api). It provides a convenient way for developers to integrate their Python applications with the Neon platform, offering methods to manage API keys, projects, branches, databases, endpoints, roles, and operations programmatically. 4 | 5 | With `neon_api`, you can automate tasks, streamline workflows, and build powerful integrations with ease. 6 | 7 | 8 | ## Installation 9 | 10 | Installation of `neon_api` is easy, with pip: 11 | 12 | ```bash 13 | $ pip install neon-api 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```python 19 | from neon_api import NeonAPI 20 | 21 | # Initialize the client. 22 | neon = NeonAPI(api_key='your_api_key') 23 | ``` 24 | 25 | **[Documentation is available on ReadTheDocs](https://neon-api-python.readthedocs.io/)**. 26 | 27 | 28 | Remember that you should never expose your api_key and handle it carefully since it gives access to sensitive data. It's better to set it as an environment variable (e.g. `NEON_API_KEY` + accompanying `neon_api.from_environ()`). 29 | 30 | 31 | ------- 32 | 33 | ### Methods of the `NeonAPI` class: 34 | 35 | - `me()`: Returns the current user. 36 | 37 | **Manage API keys**: 38 | 39 | - `api_keys()`: Returns a list of API keys. 40 | - `api_key_create(**json)`: Creates an API key. 41 | - `api_key_delete(key_id)`: Deletes a given API key. 42 | 43 | **Manage projects**: 44 | 45 | - `projects()`: Returns a list of projects. 46 | - `project(project_id)`: Returns a specific project. 47 | - `project_create(project_id, **json)`: Creates a new project. 48 | - `project_update(project_id, **json)`: Updates a given project. 49 | - `project_delete(project_id)`: Deletes a given project. 50 | - `project_permissions(project_id)`: Returns a list of permissions for a given project. 51 | - `project_permissions_grant(project_id, **json)`: Grant permissions to a given project. 52 | - `project_permissions_revoke(project_id, **json)`: Revoke permissions from a given project. 53 | - `connection_uri(project_id, database_name, role_name)`: Returns the connection string for a given project. 54 | 55 | **Manage branches**: 56 | 57 | - `branches(project_id)`: Returns a list of branches for a given project. 58 | - `branch(project_id, branch_id)`: Returns a specific branch. 59 | - `branch_create(project_id, **json)`: Creates a new branch. 60 | - `branch_update(project_id, branch_id, **json)`: Updates a given branch. 61 | - `branch_delete(project_id, branch_id)`: Deletes a given branch. 62 | - `branch_set_as_primary(project_id, branch_id)`: Sets a given branch as primary. 63 | 64 | **Manage databases**: 65 | 66 | - `databases(project_id, branch_id)`: Returns a list of databases for a given project and branch. 67 | - `database(project_id, branch_id, database_id)`: Returns a specific database. 68 | - `database_create(project_id, branch_id, **json)`: Creates a new database. 69 | - `database_update(project_id, branch_id, **json)`: Updates a given database. 70 | - `database_delete(project_id, branch_id, database_id)`: Deletes a given database. 71 | 72 | **Manage endpoints**: 73 | 74 | - `endpoints(project_id, branch_id)`: Returns a list of endpoints for a given project and branch. 75 | - `endpoint_create(project_id, branch_id, **json)`: Creates a new endpoint. 76 | - `endpoint_update(project_id, branch_id, endpoint_id, **json)`: Updates a given endpoint. 77 | - `endpoint_delete(project_id, branch_id, endpoint_id)`: Deletes a given endpoint. 78 | - `endpoint_start(project_id, branch_id, endpoint_id)`: Starts a given endpoint. 79 | - `endpoint_suspend(project_id, branch_id, endpoint_id)`: Suspends a given endpoint. 80 | 81 | **Manage roles**: 82 | 83 | - `roles(project_id, branch_id)`: Returns a list of roles for a given project and branch. 84 | - `role(project_id, branch_id, role_name)`: Returns a specific role. 85 | - `role_create(project_id, branch_id, role_name)`: Creates a new role. 86 | - `role_delete(project_id, branch_id, role_name)`: Deletes a given role. 87 | - `role_password_reveal(project_id, branch_id, role_name)`: Reveals the password for a given role. 88 | - `role_password_reset(project_id, branch_id, role_name)`: Resets the password for a given role. 89 | 90 | **Manage operations**: 91 | 92 | - `operations(project_id)`: Returns a list of operations for a given project. 93 | - `operation(project_id, operation_id)`: Returns a specific operation. 94 | 95 | **Experimental**: 96 | 97 | - `consumption()`: Returns a list of project consumption metrics. 98 | 99 | 100 | View the [Neon API documentation](https://api-docs.neon.tech/reference/getting-started-with-neon-api) for more information on the available endpoints and their parameters. 101 | 102 | 103 | ## Development 104 | 105 | First, create a virtual environment, then install the dependencies of the library with `pip`: 106 | 107 | ```bash 108 | $ pip install -r requirements.txt 109 | ``` 110 | 111 | This will install all the necessary dependencies for development. 112 | 113 | To run the tests, use the following command: 114 | 115 | ```bash 116 | # set up a .env file with your API key as per `.env.example` 117 | $ make test 118 | ``` 119 | 120 | The tests don't require an internet connection, as they are mocked using the `pytest-vcr` library. To record new cassettes, use the following command: 121 | 122 | ```bash 123 | $ make record 124 | ``` 125 | 126 | This will record new cassettes for the tests. Make sure to commit these cassettes along with your changes. 127 | 128 | ### Updating the schema 129 | 130 | In order to update the Python data types from the OpenAPI schema, you need to: 131 | 132 | ``` 133 | $ mkdir neon_client/ 134 | $ make schema 135 | # Now, take `neon_client/schema.py` and replace `neon_api/schema.py` with it. 136 | # Now, run: 137 | $ make test 138 | # You may have to run `make record` to update the fixtures. 139 | ``` 140 | 141 | ## License & Copyright 142 | 143 | [Apache 2.0 Licensed](./LICENSE). 144 | -------------------------------------------------------------------------------- /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 = . 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/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | 7 | import neon_api 8 | 9 | # -- Project information ----------------------------------------------------- 10 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 11 | 12 | project = "neon-api-python" 13 | copyright = "2024 Neon, Inc" 14 | author = "Neon, Inc" 15 | release = neon_api.__version__ 16 | 17 | # -- General configuration --------------------------------------------------- 18 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 19 | 20 | extensions = [ 21 | "sphinx.ext.autodoc", 22 | "sphinx.ext.napoleon", 23 | "sphinx.ext.viewcode", 24 | "sphinx.ext.autosummary", 25 | "sphinx.ext.autosectionlabel", 26 | ] 27 | html_theme_options = {} 28 | 29 | 30 | # autodoc_typehints = "signature" 31 | 32 | templates_path = ["_templates"] 33 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 34 | 35 | 36 | # -- Options for HTML output ------------------------------------------------- 37 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 38 | 39 | # html_theme = "alabaster" 40 | html_theme = "renku" 41 | html_static_path = ["_static"] 42 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. neon-client documentation master file, created by 2 | sphinx-quickstart on Fri Jan 26 14:52:57 2024. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ``neon-api`` — Python client for the Neon API. 7 | ============================================== 8 | 9 | This is the documentation for the ``neon_api`` package. It is a Python client for the Neon API. ``neon_api`` empowers 10 | developers by providing a comprehensive Python wrapper around the Neon API. This enables seamless integration of Neon's 11 | cloud database capabilities into Python applications, facilitating a wide range of operations such as managing API keys, 12 | projects, branches, databases, endpoints, roles, and operations directly from your codebase. 13 | 14 | With ``neon_api``, developers can: 15 | 16 | - Automate the provisioning and management of Neon cloud databases. 17 | - Programmatically control and manipulate database endpoints and roles. 18 | - Streamline workflows by integrating database operations into CI/CD pipelines. 19 | - Enhance application security by managing API keys and permissions through code. 20 | - Leverage the power of Neon's cloud database without the need for manual intervention or the Neon console. 21 | 22 | This project simplifies the complexity of interacting with the Neon API, making it more accessible for developers to 23 | build scalable, data-driven applications with ease. Pydantic dataclasses are used to represent the data structures returned 24 | by the API, and the client provides a relatively transparent interface to the Neon API. 25 | 26 | Installation 27 | ------------ 28 | 29 | To install the package, run the following command: 30 | 31 | .. code-block:: bash 32 | 33 | $ pip install neon-api 34 | 35 | .. note:: 36 | 37 | The package requires Python 3.9 or later. 38 | 39 | Usage 40 | ----- 41 | 42 | To use the package, you need to have a valid API key. You can obtain an API key from the Neon console. 43 | Once you have an API key, you can use the ``neon_api`` package to interact with the Neon API. 44 | 45 | There are two ways to initialize the client: 46 | 47 | 1. By passing the API key directly to the client. 48 | 2. By setting the API key as an environment variable and using the ``from_environ`` method. 49 | 50 | We recommend using the second method to avoid exposing your API key in your codebase: 51 | 52 | .. code-block:: bash 53 | 54 | $ export NEON_API_KEY=your_api_key 55 | 56 | Then, from Python… 57 | 58 | .. code-block:: python 59 | 60 | from neon_api import NeonAPI 61 | 62 | neon = NeonAPI.from_environ() 63 | 64 | 65 | Quickstart 66 | ---------- 67 | 68 | Below is an example of how to use the package to interact with the Neon API: 69 | 70 | .. code-block:: python 71 | 72 | from neon_api import NeonAPI 73 | 74 | # Initialize the client. 75 | neon = NeonAPI.from_environ() or NeonAPI(api_key='your_api_key') 76 | 77 | # Get the current user 78 | user = neon.me() 79 | print(user) 80 | 81 | # Get a list of API keys 82 | keys = neon.api_keys() 83 | print(keys) 84 | 85 | # Create a new API key 86 | new_key = neon.api_key_create(name="new_key") 87 | print(new_key) 88 | 89 | # Revoke an API key 90 | revoked_key = neon.api_key_revoke(api_key_id="api_key_id_to_revoke") 91 | print(revoked_key) 92 | 93 | Please reference the API Reference section for a comprehensive list of available methods and classes. 94 | 95 | API Reference 96 | ------------- 97 | 98 | The following sections provide detailed information about the classes and methods available in the ``neon_client`` package 99 | 100 | Module–level functions 101 | ////////////////////// 102 | 103 | .. automodule:: neon_api 104 | :members: 105 | :undoc-members: 106 | :show-inheritance: 107 | 108 | Classes 109 | /////// 110 | 111 | .. autoclass:: neon_api.NeonAPI 112 | :members: 113 | :undoc-members: 114 | :show-inheritance: 115 | 116 | 117 | Indices and tables 118 | ================== 119 | 120 | * :ref:`genindex` 121 | * :ref:`modindex` 122 | * :ref:`search` 123 | -------------------------------------------------------------------------------- /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=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 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 | -------------------------------------------------------------------------------- /neon_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import NeonAPI 2 | from .__version__ import __version__ 3 | from .exceptions import NeonAPIError 4 | 5 | 6 | def from_environ(): 7 | """Create a NeonAPI instance from environment variables.""" 8 | 9 | return NeonAPI.from_environ() 10 | 11 | 12 | def from_token(token): 13 | """Create a NeonAPI instance from a token.""" 14 | 15 | return NeonAPI.from_token(token) 16 | -------------------------------------------------------------------------------- /neon_api/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.3.0" 2 | -------------------------------------------------------------------------------- /neon_api/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import typing as t 3 | from datetime import datetime 4 | from functools import wraps 5 | 6 | import requests 7 | 8 | from . import schema 9 | from .utils import compact_mapping, to_iso8601 10 | from .exceptions import NeonAPIError 11 | 12 | 13 | __VERSION__ = "0.1.0" 14 | 15 | NEON_API_KEY_ENVIRON = "NEON_API_KEY" 16 | NEON_API_BASE_URL = "https://console.neon.tech/api/v2/" 17 | ENABLE_PYDANTIC = True 18 | 19 | 20 | def returns_model(model, is_array=False): 21 | """Decorator that returns a Pydantic dataclass. 22 | 23 | :param model: The Pydantic dataclass to return. 24 | :param is_array: Whether the return value is an array (default is False). 25 | :return: A Pydantic dataclass. 26 | 27 | If Pydantic is not enabled, the original return value is returned. 28 | """ 29 | 30 | def decorator(func): 31 | @wraps(func) 32 | def wrapper(*args, **kwargs): 33 | if not ENABLE_PYDANTIC: 34 | return func(*args, **kwargs) 35 | 36 | if is_array: 37 | return [model(**item) for item in func(*args, **kwargs)] 38 | else: 39 | return model(**func(*args, **kwargs)) 40 | 41 | return wrapper 42 | 43 | return decorator 44 | 45 | 46 | def returns_subkey(key): 47 | """Decorator that returns a subkey. 48 | 49 | :param key: The key to return. 50 | :return: The value of the key in the return value. 51 | """ 52 | 53 | def decorator(func): 54 | @wraps(func) 55 | def wrapper(*args, **kwargs): 56 | try: 57 | return getattr(func(*args, **kwargs), key) 58 | except AttributeError: 59 | return func(*args, **kwargs)[key] 60 | 61 | return wrapper 62 | 63 | return decorator 64 | 65 | 66 | class NeonAPI: 67 | def __init__(self, api_key: str, *, base_url: str = None): 68 | """A Neon API client. 69 | 70 | :param api_key: The API key to use for authentication. 71 | :param base_url: The base URL of the Neon API (default is https://console.neon.tech/api/v2/). 72 | """ 73 | 74 | # Set the base URL. 75 | if not base_url: 76 | base_url = NEON_API_BASE_URL 77 | 78 | # Private attributes. 79 | self._api_key = api_key 80 | self._session = requests.Session() 81 | 82 | # Public attributes. 83 | self.base_url = base_url 84 | self.user_agent = f"neon-client/python version=({__VERSION__})" 85 | 86 | def __repr__(self): 87 | return f"" 88 | 89 | def _request( 90 | self, 91 | method: str, 92 | path: str, 93 | **kwargs, 94 | ): 95 | """Send an HTTP request to the specified API path using the specified method. 96 | 97 | :param method: The HTTP method to use (e.g., "GET", "POST", "PUT", "DELETE"). 98 | :param path: The API path to send the request to. 99 | :param kwargs: Additional keyword arguments to pass to the requests.Session.request method. 100 | :return: The JSON response from the server. 101 | """ 102 | 103 | # Set HTTP headers for outgoing requests. 104 | headers = kwargs.pop("headers", {}) 105 | headers["Authorization"] = f"Bearer {self._api_key}" 106 | headers["Accept"] = "application/json" 107 | headers["Content-Type"] = "application/json" 108 | headers["User-Agent"] = self.user_agent 109 | 110 | # Send the request. 111 | r = self._session.request( 112 | method, self.base_url + path, headers=headers, **kwargs 113 | ) 114 | 115 | # Check the response status code. 116 | try: 117 | r.raise_for_status() 118 | except requests.exceptions.HTTPError: 119 | raise NeonAPIError(r.text) 120 | 121 | return r.json() 122 | 123 | def _url_join(self, *args): 124 | """Join a list of URL components into a single URL.""" 125 | 126 | return "/".join(args) 127 | 128 | @classmethod 129 | def from_environ(cls): 130 | """Create a new Neon API client from the `NEON_API_KEY` environment variable.""" 131 | 132 | return cls(os.environ[NEON_API_KEY_ENVIRON]) 133 | 134 | @returns_model(schema.CurrentUserInfoResponse) 135 | def me(self) -> t.Dict[str, t.Any]: 136 | """Get the current user. 137 | 138 | More info: https://api-docs.neon.tech/reference/getcurrentuserinfo 139 | """ 140 | 141 | return self._request("GET", "users/me") 142 | 143 | @returns_model(schema.ApiKeysListResponseItem, is_array=True) 144 | def api_keys(self) -> t.List[t.Dict[str, t.Any]]: 145 | """Get a list of API keys. 146 | 147 | :return: A dataclass representing the API key. 148 | 149 | More info: https://api-docs.neon.tech/reference/listapikeys 150 | """ 151 | 152 | return self._request("GET", "api_keys") 153 | 154 | @returns_model(schema.ApiKeyCreateResponse) 155 | def api_key_create(self, **json: dict) -> t.Dict[str, t.Any]: 156 | """Create a new API key. 157 | 158 | :param json: The JSON paypload to send to the server. 159 | :return: A dataclass representing the API key. 160 | 161 | Example usage: 162 | 163 | >>> neon.api_key_create(name="My API Key") 164 | 165 | More info: https://api-docs.neon.tech/reference/createapikey 166 | """ 167 | 168 | return self._request("POST", "api_keys", json=json) 169 | 170 | @returns_model(schema.ApiKeyRevokeResponse) 171 | def api_key_revoke(self, api_key_id: str) -> t.Dict[str, t.Any]: 172 | """Revoke an API key. 173 | 174 | :param api_key_id: The ID of the API key to revoke. 175 | :return: A dataclass representing the API key. 176 | 177 | More info: https://api-docs.neon.tech/reference/revokeapikey 178 | """ 179 | return self._request("DELETE", f"api_keys/{ api_key_id }") 180 | 181 | @returns_model(schema.ProjectsResponse) 182 | def projects( 183 | self, 184 | *, 185 | shared: bool = False, 186 | cursor: str = None, 187 | limit: int = None, 188 | ) -> t.List[t.Dict[str, t.Any]]: 189 | """Get a list of projects. If shared is True, get a list of shared projects. 190 | 191 | :param shared: Whether to retrieve shared projects (default is False). 192 | :param cursor: The cursor for pagination (default is None). 193 | :param limit: The maximum number of projects to retrieve (default is None). 194 | :return: A list of dataclasses representing the projects. 195 | 196 | More info: https://api-docs.neon.tech/reference/listprojects 197 | """ 198 | 199 | r_path = "projects" if not shared else "projects/shared" 200 | r_params = compact_mapping({"cursor": cursor, "limit": limit}) 201 | 202 | return self._request("GET", r_path, params=r_params) 203 | 204 | @returns_model(schema.ProjectResponse) 205 | def project(self, project_id: str) -> t.Dict[str, t.Any]: 206 | """Get a project. 207 | 208 | :param project_id: The ID of the project. 209 | :return: A dataclass representing the project. 210 | 211 | More info: https://api-docs.neon.tech/reference/getproject 212 | """ 213 | 214 | r_path = f"projects/{project_id}" 215 | 216 | return self._request("GET", r_path) 217 | 218 | @returns_model(schema.ConnectionURIResponse) 219 | def connection_uri( 220 | self, 221 | project_id: str, 222 | database_name: str = None, 223 | role_name: str = None, 224 | pooled: bool = False, 225 | ) -> t.Dict[str, t.Any]: 226 | """Get a connection URI for a project. 227 | 228 | :param project_id: The ID of the project. 229 | :param database_name: The name of the database. 230 | :param role_name: The name of the role. 231 | :return: A dataclass representing the connection URI. 232 | 233 | More info: https://api-docs.neon.tech/reference/getconnectionuri 234 | """ 235 | r_params = compact_mapping( 236 | {"database_name": database_name, "role_name": role_name, "pooled": pooled} 237 | ) 238 | return self._request( 239 | "GET", f"projects/{project_id}/connection_uri", params=r_params 240 | ) 241 | 242 | @returns_model(schema.ProjectResponse) 243 | def project_create(self, **json: dict) -> t.Dict[str, t.Any]: 244 | """Create a new project. Accepts all keyword arguments for json body. 245 | 246 | :param json: The JSON paypload to send to the server. 247 | :return: A dataclass representing the project. 248 | 249 | More info: https://api-docs.neon.tech/reference/createproject 250 | """ 251 | 252 | return self._request("POST", "projects", json=json) 253 | 254 | @returns_model(schema.ProjectResponse) 255 | def project_update(self, project_id: str, **json: dict) -> t.Dict[str, t.Any]: 256 | """Updates a project. Accepts all keyword arguments for json body. 257 | 258 | :param project_id: The ID of the project. 259 | :param json: The JSON paypload to send to the server. 260 | :return: A dataclass representing the project. 261 | 262 | More info: https://api-docs.neon.tech/reference/updateproject""" 263 | 264 | return self._request("PATCH", f"projects/{ project_id }", json=json) 265 | 266 | @returns_model(schema.ProjectResponse) 267 | def project_delete(self, project_id: str) -> t.Dict[str, t.Any]: 268 | """Delete a project. 269 | 270 | :param project_id: The ID of the project. 271 | :return: A dataclass representing the project. 272 | 273 | More info: https://api-docs.neon.tech/reference/deleteproject 274 | """ 275 | 276 | return self._request("DELETE", f"projects/{ project_id }") 277 | 278 | @returns_model(schema.ProjectPermissions) 279 | def project_permissions(self, project_id: str) -> t.Dict[str, t.Any]: 280 | """Get a project permissions. 281 | 282 | :param project_id: The ID of the project. 283 | :return: A dataclass representing the project permissions. 284 | 285 | More info: https://api-docs.neon.tech/reference/listprojectpermissions 286 | """ 287 | return self._request("GET", f"projects/{ project_id }/permissions") 288 | 289 | @returns_model(schema.ProjectPermission) 290 | def project_permissions_grant( 291 | self, project_id: str, **json: dict 292 | ) -> t.Dict[str, t.Any]: 293 | """Update a project permissions. Accepts all keyword arguments for json body. 294 | 295 | :param project_id: The ID of the project. 296 | :param json: The JSON paypload to send to the server. 297 | :return: A dataclass representing the project permissions. 298 | 299 | More info: https://api-docs.neon.tech/reference/grantpermissiontoproject 300 | """ 301 | return self._request("POST", f"projects/{ project_id }/permissions", json=json) 302 | 303 | @returns_model(schema.ProjectPermission) 304 | def project_permissions_revoke( 305 | self, project_id: str, **json: dict 306 | ) -> t.Dict[str, t.Any]: 307 | """Update a project permissions. Accepts all keyword arguments for json body. 308 | 309 | :param project_id: The ID of the project. 310 | :param json: The JSON paypload to send to the server. 311 | :return: A dataclass representing the project permissions. 312 | 313 | More info: https://api-docs.neon.tech/reference/revokepermissionfromproject 314 | """ 315 | return self._request( 316 | "DELETE", f"projects/{ project_id }/permissions", json=json 317 | ) 318 | 319 | @returns_model(schema.BranchesResponse) 320 | def branches( 321 | self, 322 | project_id: str, 323 | *, 324 | cursor: str = None, 325 | limit: int = None, 326 | ) -> t.Dict[str, t.Any]: 327 | """Get a list of branches. 328 | 329 | :param project_id: The ID of the project. 330 | :param cursor: The cursor for pagination (default is None). 331 | :param limit: The maximum number of projects to retrieve (default is None). 332 | :return: A list of dataclasses representing the projects. 333 | 334 | More info: https://api-docs.neon.tech/reference/listprojectbranches 335 | """ 336 | 337 | # Construct the request path and parameters. 338 | r_path = self._url_join("projects", project_id, "branches") 339 | r_params = compact_mapping({"cursor": cursor, "limit": limit}) 340 | 341 | # Make the request. 342 | return self._request("GET", r_path, params=r_params) 343 | 344 | @returns_model(schema.BranchResponse) 345 | def branch(self, project_id: str, branch_id: str) -> t.Dict[str, t.Any]: 346 | """Get a branch. 347 | 348 | :param project_id: The ID of the project. 349 | :param branch_id: The ID of the branch. 350 | :return: A dataclass representing the branch. 351 | 352 | More info: https://api-docs.neon.tech/reference/getprojectbranch 353 | """ 354 | 355 | # Construct the request path. 356 | r_path = self._url_join("projects", project_id, "branches", branch_id) 357 | 358 | # Make the request. 359 | return self._request("GET", r_path) 360 | 361 | @returns_model(schema.BranchOperations) 362 | def branch_create(self, project_id: str, **json: dict) -> t.Dict[str, t.Any]: 363 | """Create a new branch. Accepts all keyword arguments for json body. 364 | 365 | :param project_id: The ID of the project. 366 | :param json: The JSON paypload to send to the server. 367 | :return: A dataclass representing the branch. 368 | 369 | More info: https://api-docs.neon.tech/reference/createprojectbranch 370 | """ 371 | return self._request("POST", f"projects/{ project_id }/branches", json=json) 372 | 373 | @returns_model(schema.BranchOperations) 374 | def branch_update( 375 | self, project_id: str, branch_id: str, **json: dict 376 | ) -> t.Dict[str, t.Any]: 377 | """Update a branch by branch_id. Accepts all keyword arguments for json body. 378 | 379 | :param project_id: The ID of the project. 380 | :param branch_id: The ID of the branch. 381 | :param json: The JSON paypload to send to the server. 382 | :return: A dataclass representing the branch. 383 | 384 | More info: https://api-docs.neon.tech/reference/updateprojectbranch 385 | """ 386 | 387 | return self._request( 388 | "PATCH", f"projects/{ project_id }/branches/{ branch_id }", json=json 389 | ) 390 | 391 | @returns_model(schema.BranchOperations) 392 | def branch_delete(self, project_id: str, branch_id: str) -> t.Dict[str, t.Any]: 393 | """Delete a branch by branch_id. 394 | 395 | :param project_id: The ID of the project. 396 | :param branch_id: The ID of the branch. 397 | :return: A dataclass representing the branch. 398 | 399 | More info: https://api-docs.neon.tech/reference/deleteprojectbranch 400 | """ 401 | return self._request( 402 | "DELETE", f"projects/{ project_id }/branches/{ branch_id }" 403 | ) 404 | 405 | @returns_model(schema.BranchOperations) 406 | def branch_set_as_primary( 407 | self, project_id: str, branch_id: str 408 | ) -> t.Dict[str, t.Any]: 409 | """Set a branch as primary by branch_id. 410 | 411 | :param project_id: The ID of the project. 412 | :param branch_id: The ID of the branch. 413 | :return: A dataclass representing the branch. 414 | 415 | More info: https://api-docs.neon.tech/reference/setprimaryprojectbranch""" 416 | 417 | return self._request( 418 | "POST", f"projects/{ project_id }/branches/{ branch_id }/set_as_primary" 419 | ) 420 | 421 | @returns_model(schema.DatabasesResponse) 422 | def databases( 423 | self, 424 | project_id: str, 425 | branch_id: str, 426 | *, 427 | cursor: str = None, 428 | limit: int = None, 429 | ) -> t.List[t.Dict[str, t.Any]]: 430 | """Get a list of databases. 431 | 432 | :param project_id: The ID of the project. 433 | :param branch_id: The ID of the branch. 434 | :param cursor: The cursor for pagination (default is None). 435 | :param limit: The maximum number of projects to retrieve (default is None). 436 | :return: A list of dataclasses representing the database. 437 | 438 | More info: https://api-docs.neon.tech/reference/listprojectbranchdatabases 439 | """ 440 | 441 | # Construct the request path and parameters. 442 | r_path = self._url_join( 443 | "projects", project_id, "branches", branch_id, "databases" 444 | ) 445 | r_params = compact_mapping({"cursor": cursor, "limit": limit}) 446 | 447 | # Make the request. 448 | return self._request("GET", r_path, params=r_params) 449 | 450 | @returns_model(schema.DatabaseResponse) 451 | def database( 452 | self, project_id: str, branch_id: str, database_id: str 453 | ) -> t.Dict[str, t.Any]: 454 | """Get a database. 455 | 456 | :param project_id: The ID of the project. 457 | :param branch_id: The ID of the branch. 458 | :param database_id: The ID of the database. 459 | :return: A dataclass representing the database. 460 | 461 | More info: https://api-docs.neon.tech/reference/getprojectbranchdatabase 462 | """ 463 | 464 | # Construct the request path. 465 | r_path = self._url_join( 466 | "projects", project_id, "branches", branch_id, "databases", database_id 467 | ) 468 | 469 | # Make the request. 470 | return self._request("GET", r_path) 471 | 472 | @returns_model(schema.DatabaseResponse) 473 | def database_create( 474 | self, project_id: str, branch_id: str, **json: dict 475 | ) -> t.Dict[str, t.Any]: 476 | """Create a new database. Accepts all keyword arguments for json body. 477 | 478 | :param project_id: The ID of the project. 479 | :param branch_id: The ID of the branch. 480 | :param json: The JSON paypload to send to the server. 481 | :return: A dataclass representing the database. 482 | 483 | More info: https://api-docs.neon.tech/reference/createprojectbranchdatabase 484 | """ 485 | 486 | return self._request( 487 | "POST", 488 | f"projects/{ project_id }/branches/{ branch_id }/databases", 489 | json=json, 490 | ) 491 | 492 | @returns_model(schema.DatabaseResponse) 493 | def database_update( 494 | self, project_id: str, branch_id: str, database_id: str, **json: dict 495 | ) -> t.Dict[str, t.Any]: 496 | """Update a database. Accepts all keyword arguments for json body. 497 | 498 | :param project_id: The ID of the project. 499 | :param branch_id: The ID of the branch. 500 | :param database_id: The ID of the database. 501 | :param json: The JSON paypload to send to the server. 502 | :return: A dataclass representing the database. 503 | 504 | More info: https://api-docs.neon.tech/reference/updateprojectbranchdatabase 505 | """ 506 | 507 | return self._request( 508 | "PATCH", 509 | f"projects/{ project_id }/branches/{ branch_id }/databases/{ database_id }", 510 | json=json, 511 | ) 512 | 513 | @returns_model(schema.DatabaseResponse) 514 | def database_delete( 515 | self, project_id: str, branch_id: str, database_id: str 516 | ) -> t.Dict[str, t.Any]: 517 | """Delete a database by database_id. 518 | 519 | :param project_id: The ID of the project. 520 | :param branch_id: The ID of the branch. 521 | :param database_id: The ID of the database. 522 | :return: A dataclass representing the database. 523 | 524 | More info: https://api-docs.neon.tech/reference/deleteprojectbranchdatabase 525 | """ 526 | 527 | return self._request( 528 | "DELETE", 529 | f"projects/{ project_id }/branches/{ branch_id }/databases/{ database_id }", 530 | ) 531 | 532 | @returns_model(schema.EndpointsResponse) 533 | def endpoints(self, project_id: str) -> t.Dict[str, t.Any]: 534 | """Get a list of endpoints for a given branch 535 | 536 | :param project_id: The ID of the project. 537 | :return: A list of dataclasses representing the endpoints. 538 | 539 | More info: https://api-docs.neon.tech/reference/listprojectendpoints 540 | """ 541 | return self._request("GET", f"projects/{ project_id }/endpoints") 542 | 543 | @returns_model(schema.EndpointResponse) 544 | def endpoint(self, project_id: str, endpoint_id: str) -> t.Dict[str, t.Any]: 545 | """Get an endpoint for a given branch. 546 | 547 | :param project_id: The ID of the project. 548 | :param endpoint_id: The ID of the endpoint. 549 | :return: A dataclass representing the endpoint. 550 | 551 | More info: https://api-docs.neon.tech/reference/getprojectendpoint 552 | """ 553 | return self._request( 554 | "GET", 555 | f"projects/{ project_id }/endpoints/{ endpoint_id }", 556 | ) 557 | 558 | @returns_model(schema.EndpointOperations) 559 | def endpoint_create( 560 | self, 561 | project_id: str, 562 | **json: dict, 563 | ) -> t.Dict[str, t.Any]: 564 | """Create a new endpoint. Accepts all keyword arguments for json body. 565 | 566 | :param project_id: The ID of the project. 567 | :param json: The JSON paypload to send to the server. 568 | :return: A dataclass representing the endpoint. 569 | 570 | More info: https://api-docs.neon.tech/reference/createprojectendpoint 571 | """ 572 | 573 | return self._request("POST", f"projects/{ project_id }/endpoints", json=json) 574 | 575 | @returns_model(schema.EndpointOperations) 576 | def endpoint_delete(self, project_id: str, endpoint_id: str) -> t.Dict[str, t.Any]: 577 | """Delete an endpoint by endpoint_id. 578 | 579 | :param project_id: The ID of the project. 580 | :param endpoint_id: The ID of the endpoint. 581 | :return: A dataclass representing the endpoint. 582 | 583 | More info: https://api-docs.neon.tech/reference/deleteprojectendpoint 584 | """ 585 | 586 | return self._request( 587 | "DELETE", 588 | f"projects/{ project_id }/endpoints/{ endpoint_id }", 589 | ) 590 | 591 | @returns_model(schema.EndpointOperations) 592 | def endpoint_update( 593 | self, project_id: str, endpoint_id: str, **json: dict 594 | ) -> t.Dict[str, t.Any]: 595 | """Update an endpoint. Accepts all keyword arguments for json body. 596 | 597 | :param project_id: The ID of the project. 598 | :param endpoint_id: The ID of the endpoint. 599 | :param json: The JSON paypload to send to the server. 600 | :return: A dataclass representing the endpoint. 601 | 602 | More info: https://api-docs.neon.tech/reference/updateprojectendpoint 603 | """ 604 | 605 | return self._request( 606 | "PATCH", 607 | f"projects/{ project_id }/endpoints/{ endpoint_id }", 608 | json=json, 609 | ) 610 | 611 | @returns_model(schema.EndpointOperations) 612 | def endpoint_start(self, project_id: str, endpoint_id: str): 613 | """Start an endpoint by endpoint_id. 614 | 615 | :param project_id: The ID of the project. 616 | :param endpoint_id: The ID of the endpoint. 617 | :return: A dataclass representing the endpoint. 618 | 619 | More info: https://api-docs.neon.tech/reference/startprojectendpoint""" 620 | 621 | return self._request( 622 | "POST", 623 | f"projects/{ project_id }/endpoints/{ endpoint_id }/start", 624 | ) 625 | 626 | @returns_model(schema.EndpointOperations) 627 | def endpoint_suspend(self, project_id: str, endpoint_id: str): 628 | """Suspend an endpoint by endpoint_id. 629 | 630 | :param project_id: The ID of the project. 631 | :param endpoint_id: The ID of the endpoint. 632 | :return: A dataclass representing the endpoint. 633 | 634 | More info: https://api-docs.neon.tech/reference/suspendprojectendpoint 635 | """ 636 | 637 | return self._request( 638 | "POST", 639 | f"projects/{ project_id }/endpoints/{ endpoint_id }/suspend", 640 | ) 641 | 642 | @returns_model(schema.RolesResponse) 643 | def roles(self, project_id: str, branch_id: str) -> t.Dict[str, t.Any]: 644 | """Get a list of roles for a given branch. 645 | 646 | :param project_id: The ID of the project. 647 | :param branch_id: The ID of the branch. 648 | :return: A list of dataclasses representing the roles. 649 | 650 | More info: https://api-docs.neon.tech/reference/listprojectbranchroles 651 | """ 652 | 653 | return self._request( 654 | "GET", f"projects/{ project_id }/branches/{ branch_id }/roles" 655 | ) 656 | 657 | @returns_model(schema.RoleResponse) 658 | def role( 659 | self, project_id: str, branch_id: str, role_name: str 660 | ) -> t.Dict[str, t.Any]: 661 | """Get a role for a given branch. 662 | 663 | :param project_id: The ID of the project. 664 | :param branch_id: The ID of the branch. 665 | :param role_name: The name of the role. 666 | :return: A dataclass representing the role. 667 | 668 | More info: https://api-docs.neon.tech/reference/getprojectbranchrole 669 | 670 | """ 671 | return self._request( 672 | "GET", f"projects/{ project_id }/branches/{ branch_id }/roles/{ role_name }" 673 | ) 674 | 675 | @returns_model(schema.RoleOperations) 676 | def role_create( 677 | self, 678 | project_id: str, 679 | branch_id: str, 680 | role_name: str, 681 | ) -> t.Dict[str, t.Any]: 682 | """Create a new role. Accepts all keyword arguments for json body. 683 | 684 | :param project_id: The ID of the project. 685 | :param branch_id: The ID of the branch. 686 | :param role_name: The name of the role. 687 | :return: A dataclass representing the role. 688 | 689 | More info: https://api-docs.neon.tech/reference/createprojectbranchrole 690 | """ 691 | 692 | return self._request( 693 | "POST", 694 | f"projects/{ project_id }/branches/{ branch_id }/roles", 695 | json={"role": {"name": role_name}}, 696 | ) 697 | 698 | @returns_model(schema.RoleOperations) 699 | def role_delete( 700 | self, 701 | project_id: str, 702 | branch_id: str, 703 | role_name: str, 704 | ) -> t.Dict[str, t.Any]: 705 | """Delete a role by given role name. 706 | 707 | :param project_id: The ID of the project. 708 | :param branch_id: The ID of the branch. 709 | :param role_name: The name of the role. 710 | :return: A dataclass representing the role. 711 | 712 | More info: https://api-docs.neon.tech/reference/deleteprojectbranchrole 713 | """ 714 | 715 | return self._request( 716 | "DELETE", 717 | f"projects/{ project_id }/branches/{ branch_id }/roles/{ role_name }", 718 | ) 719 | 720 | @returns_model(schema.RolePasswordResponse) 721 | def role_password_reveal( 722 | self, 723 | project_id: str, 724 | branch_id: str, 725 | role_name: str, 726 | ) -> t.Dict[str, t.Any]: 727 | """Get a role password. 728 | 729 | :param project_id: The ID of the project. 730 | :param branch_id: The ID of the branch. 731 | :param role_name: The name of the role. 732 | :return: A dataclass representing the role password. 733 | 734 | More info: https://api-docs.neon.tech/reference/getprojectbranchrolepassword""" 735 | 736 | return self._request( 737 | "POST", 738 | f"projects/{ project_id }/branches/{ branch_id }/roles/{ role_name }/reveal_password", 739 | ) 740 | 741 | @returns_model(schema.RoleOperations) 742 | def role_password_reset( 743 | self, 744 | project_id: str, 745 | branch_id: str, 746 | role_name: str, 747 | ) -> t.Dict[str, t.Any]: 748 | """Reset a role password. 749 | 750 | :param project_id: The ID of the project. 751 | :param branch_id: The ID of the branch. 752 | :param role_name: The name of the role. 753 | :return: A dataclass representing the role. 754 | 755 | More info: https://api-docs.neon.tech/reference/resetprojectbranchrolepassword 756 | """ 757 | 758 | return self._request( 759 | "POST", 760 | f"projects/{ project_id }/branches/{ branch_id }/roles/{ role_name }/reset_password", 761 | ) 762 | 763 | @returns_model(schema.OperationsResponse) 764 | def operations( 765 | self, 766 | project_id: str, 767 | *, 768 | cursor: str = None, 769 | limit: int = None, 770 | ) -> t.Dict[str, t.Any]: 771 | """Get a list of operations. 772 | 773 | :param project_id: The ID of the project. 774 | :param cursor: The cursor for pagination (default is None). 775 | :param limit: The maximum number of projects to retrieve (default is None). 776 | :return: A list of dataclasses representing the operations. 777 | 778 | More info: https://api-docs.neon.tech/reference/listprojectoperations 779 | """ 780 | 781 | r_params = compact_mapping({"cursor": cursor, "limit": limit}) 782 | return self._request( 783 | "GET", f"projects/{ project_id }/operations", params=r_params 784 | ) 785 | 786 | @returns_model(schema.OperationResponse) 787 | def operation(self, project_id: str, operation_id: str) -> t.Dict[str, t.Any]: 788 | """Get an operation. 789 | 790 | :param project_id: The ID of the project. 791 | :param operation_id: The ID of the operation. 792 | :return: A dataclass representing the operation. 793 | 794 | More info: https://api-docs.neon.tech/reference/getprojectoperation 795 | """ 796 | 797 | return self._request( 798 | "GET", f"projects/{ project_id }/operations/{ operation_id }" 799 | ) 800 | -------------------------------------------------------------------------------- /neon_api/exceptions.py: -------------------------------------------------------------------------------- 1 | from requests.exceptions import HTTPError 2 | 3 | 4 | class NeonAPIError(HTTPError): 5 | pass 6 | -------------------------------------------------------------------------------- /neon_api/schema.py: -------------------------------------------------------------------------------- 1 | # generated by datamodel-codegen: 2 | # filename: v2.json 3 | # timestamp: 2025-01-15T15:35:52+00:00 4 | 5 | from __future__ import annotations 6 | 7 | from dataclasses import dataclass 8 | from datetime import datetime 9 | from enum import Enum 10 | from typing import Optional, Union 11 | 12 | from pydantic.dataclasses import dataclass 13 | 14 | FeatureFlags = Optional[dict[str, Union[bool, str]]] 15 | 16 | 17 | @dataclass 18 | class Pagination: 19 | cursor: str 20 | 21 | 22 | @dataclass 23 | class EmptyResponse: 24 | pass 25 | 26 | 27 | @dataclass 28 | class AddProjectJWKSRequest: 29 | jwks_url: str 30 | provider_name: str 31 | role_names: list[str] 32 | branch_id: Optional[str] = None 33 | jwt_audience: Optional[str] = None 34 | 35 | 36 | @dataclass 37 | class JWKS: 38 | id: str 39 | project_id: str 40 | jwks_url: str 41 | provider_name: str 42 | created_at: str 43 | updated_at: str 44 | branch_id: Optional[str] = None 45 | jwt_audience: Optional[str] = None 46 | 47 | 48 | @dataclass 49 | class ProjectJWKSResponse: 50 | jwks: list[JWKS] 51 | 52 | 53 | @dataclass 54 | class ApiKeyCreateRequest: 55 | key_name: str 56 | 57 | 58 | @dataclass 59 | class OrgApiKeyCreateRequest(ApiKeyCreateRequest): 60 | project_id: Optional[str] = None 61 | 62 | 63 | @dataclass 64 | class ApiKeyCreateResponse: 65 | id: int 66 | key: str 67 | name: str 68 | created_at: str 69 | created_by: str 70 | 71 | 72 | @dataclass 73 | class OrgApiKeyCreateResponse(ApiKeyCreateResponse): 74 | project_id: Optional[str] = None 75 | 76 | 77 | @dataclass 78 | class ApiKeyRevokeResponse: 79 | id: int 80 | name: str 81 | created_at: str 82 | created_by: str 83 | last_used_from_addr: str 84 | revoked: bool 85 | last_used_at: Optional[str] = None 86 | 87 | 88 | @dataclass 89 | class OrgApiKeyRevokeResponse(ApiKeyRevokeResponse): 90 | project_id: Optional[str] = None 91 | 92 | 93 | @dataclass 94 | class ApiKeyCreatorData: 95 | id: str 96 | name: str 97 | image: str 98 | 99 | 100 | class OperationAction(Enum): 101 | create_compute = 'create_compute' 102 | create_timeline = 'create_timeline' 103 | start_compute = 'start_compute' 104 | suspend_compute = 'suspend_compute' 105 | apply_config = 'apply_config' 106 | check_availability = 'check_availability' 107 | delete_timeline = 'delete_timeline' 108 | create_branch = 'create_branch' 109 | tenant_ignore = 'tenant_ignore' 110 | tenant_attach = 'tenant_attach' 111 | tenant_detach = 'tenant_detach' 112 | tenant_reattach = 'tenant_reattach' 113 | replace_safekeeper = 'replace_safekeeper' 114 | disable_maintenance = 'disable_maintenance' 115 | apply_storage_config = 'apply_storage_config' 116 | prepare_secondary_pageserver = 'prepare_secondary_pageserver' 117 | switch_pageserver = 'switch_pageserver' 118 | detach_parent_branch = 'detach_parent_branch' 119 | timeline_archive = 'timeline_archive' 120 | timeline_unarchive = 'timeline_unarchive' 121 | start_reserved_compute = 'start_reserved_compute' 122 | sync_dbs_and_roles_from_compute = 'sync_dbs_and_roles_from_compute' 123 | 124 | 125 | class OperationStatus(Enum): 126 | scheduling = 'scheduling' 127 | running = 'running' 128 | finished = 'finished' 129 | failed = 'failed' 130 | error = 'error' 131 | cancelling = 'cancelling' 132 | cancelled = 'cancelled' 133 | skipped = 'skipped' 134 | 135 | 136 | @dataclass 137 | class Branch: 138 | name: Optional[str] = None 139 | role_name: Optional[str] = None 140 | database_name: Optional[str] = None 141 | 142 | 143 | @dataclass 144 | class ProjectPermission: 145 | id: str 146 | granted_to_email: str 147 | granted_at: str 148 | revoked_at: Optional[str] = None 149 | 150 | 151 | @dataclass 152 | class ProjectPermissions: 153 | project_permissions: list[ProjectPermission] 154 | 155 | 156 | @dataclass 157 | class GrantPermissionToProjectRequest: 158 | email: str 159 | 160 | 161 | @dataclass 162 | class ConsumptionHistoryPerTimeframe: 163 | timeframe_start: str 164 | timeframe_end: str 165 | active_time_seconds: int 166 | compute_time_seconds: int 167 | written_data_bytes: int 168 | synthetic_storage_size_bytes: int 169 | data_storage_bytes_hour: Optional[int] = None 170 | 171 | 172 | class ConsumptionHistoryGranularity(Enum): 173 | hourly = 'hourly' 174 | daily = 'daily' 175 | monthly = 'monthly' 176 | 177 | 178 | @dataclass 179 | class Limits: 180 | active_time: int 181 | max_projects: int 182 | max_branches: int 183 | max_protected_branches: int 184 | max_autoscaling_cu: float 185 | max_fixed_size_cu: float 186 | cpu_seconds: int 187 | max_compute_time_non_primary: int 188 | max_active_endpoints: int 189 | max_read_only_endpoints: int 190 | max_allowed_ips: int 191 | max_vpc_endpoints_per_region: int 192 | max_monitoring_retention_hours: int 193 | max_history_retention_seconds: int 194 | min_autosuspend_seconds: int 195 | max_data_transfer: int 196 | min_idle_seconds_to_autoarchive: int 197 | min_age_seconds_to_autoarchive: int 198 | max_branch_roles: int 199 | max_branch_databases: int 200 | max_concurrent_scheduled_operation_chains_per_project: int 201 | max_concurrent_executing_operation_chains_per_project: int 202 | 203 | 204 | @dataclass 205 | class CreatedBy: 206 | name: Optional[str] = None 207 | image: Optional[str] = None 208 | 209 | 210 | @dataclass 211 | class Branch2: 212 | parent_id: Optional[str] = None 213 | name: Optional[str] = None 214 | parent_lsn: Optional[str] = None 215 | parent_timestamp: Optional[str] = None 216 | protected: Optional[bool] = None 217 | archived: Optional[bool] = None 218 | init_source: Optional[str] = None 219 | 220 | 221 | @dataclass 222 | class Branch3: 223 | name: Optional[str] = None 224 | protected: Optional[bool] = None 225 | 226 | 227 | @dataclass 228 | class BranchUpdateRequest: 229 | branch: Branch3 230 | 231 | 232 | @dataclass 233 | class BranchRestoreRequest: 234 | source_branch_id: str 235 | source_lsn: Optional[str] = None 236 | source_timestamp: Optional[str] = None 237 | preserve_under_name: Optional[str] = None 238 | 239 | 240 | @dataclass 241 | class BranchSchemaResponse: 242 | sql: Optional[str] = None 243 | 244 | 245 | @dataclass 246 | class BranchSchemaCompareResponse: 247 | diff: Optional[str] = None 248 | 249 | 250 | @dataclass 251 | class BranchesCountResponse: 252 | count: int 253 | 254 | 255 | @dataclass 256 | class ConnectionParameters: 257 | database: str 258 | password: str 259 | role: str 260 | host: str 261 | pooler_host: str 262 | 263 | 264 | @dataclass 265 | class ConnectionDetails: 266 | connection_uri: str 267 | connection_parameters: ConnectionParameters 268 | 269 | 270 | @dataclass 271 | class ConnectionURIResponse: 272 | uri: str 273 | 274 | 275 | class EndpointState(Enum): 276 | init = 'init' 277 | active = 'active' 278 | idle = 'idle' 279 | 280 | 281 | class EndpointType(Enum): 282 | read_only = 'read_only' 283 | read_write = 'read_write' 284 | 285 | 286 | class EndpointPoolerMode(Enum): 287 | transaction = 'transaction' 288 | 289 | 290 | @dataclass 291 | class AllowedIps: 292 | ips: Optional[list[str]] = None 293 | protected_branches_only: Optional[bool] = None 294 | 295 | 296 | @dataclass 297 | class MaintenanceWindow: 298 | weekdays: list[int] 299 | start_time: str 300 | end_time: str 301 | 302 | 303 | @dataclass 304 | class ConnectionURIsResponse: 305 | connection_uris: list[ConnectionDetails] 306 | 307 | 308 | @dataclass 309 | class ConnectionURIsOptionalResponse: 310 | connection_uris: Optional[list[ConnectionDetails]] = None 311 | 312 | 313 | @dataclass 314 | class VPCEndpoint: 315 | vpc_endpoint_id: str 316 | label: str 317 | 318 | 319 | @dataclass 320 | class VPCEndpointDetails: 321 | vpc_endpoint_id: str 322 | label: str 323 | state: str 324 | num_restricted_projects: int 325 | example_restricted_projects: list[str] 326 | 327 | 328 | @dataclass 329 | class VPCEndpointAssignment: 330 | label: str 331 | 332 | 333 | @dataclass 334 | class EndpointPasswordlessSessionAuthRequest: 335 | session_id: str 336 | 337 | 338 | Duration = int 339 | 340 | 341 | @dataclass 342 | class StatementData: 343 | truncated: bool 344 | fields: Optional[list[str]] = None 345 | rows: Optional[list[list[str]]] = None 346 | 347 | 348 | @dataclass 349 | class ExplainData: 350 | QUERY_PLAN: str 351 | 352 | 353 | @dataclass 354 | class Role: 355 | branch_id: str 356 | name: str 357 | created_at: str 358 | updated_at: str 359 | password: Optional[str] = None 360 | protected: Optional[bool] = None 361 | 362 | 363 | @dataclass 364 | class Role1: 365 | name: str 366 | 367 | 368 | @dataclass 369 | class RoleCreateRequest: 370 | role: Role1 371 | 372 | 373 | @dataclass 374 | class RoleResponse: 375 | role: Role 376 | 377 | 378 | @dataclass 379 | class JWKSResponse: 380 | jwks: JWKS 381 | 382 | 383 | @dataclass 384 | class RolesResponse: 385 | roles: list[Role] 386 | 387 | 388 | @dataclass 389 | class RolePasswordResponse: 390 | password: str 391 | 392 | 393 | class Brand(Enum): 394 | amex = 'amex' 395 | diners = 'diners' 396 | discover = 'discover' 397 | jcb = 'jcb' 398 | mastercard = 'mastercard' 399 | unionpay = 'unionpay' 400 | unknown = 'unknown' 401 | visa = 'visa' 402 | 403 | 404 | @dataclass 405 | class PaymentSourceBankCard: 406 | last4: str 407 | brand: Optional[Brand] = None 408 | exp_month: Optional[int] = None 409 | exp_year: Optional[int] = None 410 | 411 | 412 | @dataclass 413 | class PaymentSource: 414 | type: str 415 | card: Optional[PaymentSourceBankCard] = None 416 | 417 | 418 | class BillingAccountState(Enum): 419 | UNKNOWN = 'UNKNOWN' 420 | active = 'active' 421 | suspended = 'suspended' 422 | deactivated = 'deactivated' 423 | deleted = 'deleted' 424 | 425 | 426 | class BillingSubscriptionType(Enum): 427 | UNKNOWN = 'UNKNOWN' 428 | direct_sales = 'direct_sales' 429 | aws_marketplace = 'aws_marketplace' 430 | free_v2 = 'free_v2' 431 | launch = 'launch' 432 | scale = 'scale' 433 | business = 'business' 434 | vercel_pg_legacy = 'vercel_pg_legacy' 435 | 436 | 437 | class BillingPaymentMethod(Enum): 438 | UNKNOWN = 'UNKNOWN' 439 | none = 'none' 440 | stripe = 'stripe' 441 | direct_payment = 'direct_payment' 442 | aws_mp = 'aws_mp' 443 | azure_mp = 'azure_mp' 444 | vercel_mp = 'vercel_mp' 445 | staff = 'staff' 446 | trial = 'trial' 447 | sponsorship = 'sponsorship' 448 | 449 | 450 | @dataclass 451 | class Database: 452 | id: int 453 | branch_id: str 454 | name: str 455 | owner_name: str 456 | created_at: str 457 | updated_at: str 458 | 459 | 460 | @dataclass 461 | class Database1: 462 | name: str 463 | owner_name: str 464 | 465 | 466 | @dataclass 467 | class DatabaseCreateRequest: 468 | database: Database1 469 | 470 | 471 | @dataclass 472 | class Database2: 473 | name: Optional[str] = None 474 | owner_name: Optional[str] = None 475 | 476 | 477 | @dataclass 478 | class DatabaseUpdateRequest: 479 | database: Database2 480 | 481 | 482 | @dataclass 483 | class DatabaseResponse: 484 | database: Database 485 | 486 | 487 | @dataclass 488 | class DatabasesResponse: 489 | databases: list[Database] 490 | 491 | 492 | class MemberRole(Enum): 493 | admin = 'admin' 494 | member = 'member' 495 | 496 | 497 | @dataclass 498 | class Member: 499 | id: str 500 | user_id: str 501 | org_id: str 502 | role: MemberRole 503 | joined_at: Optional[str] = None 504 | 505 | 506 | @dataclass 507 | class MemberUserInfo: 508 | email: str 509 | 510 | 511 | @dataclass 512 | class MemberWithUser: 513 | member: Member 514 | user: MemberUserInfo 515 | 516 | 517 | @dataclass 518 | class Organization: 519 | id: str 520 | name: str 521 | handle: str 522 | plan: str 523 | created_at: str 524 | managed_by: str 525 | updated_at: str 526 | 527 | 528 | @dataclass 529 | class OrganizationsResponse: 530 | organizations: list[Organization] 531 | 532 | 533 | @dataclass 534 | class OrganizationsUpdateRequest: 535 | name: str 536 | 537 | 538 | @dataclass 539 | class OrganizationInviteCreateRequest: 540 | email: str 541 | role: MemberRole 542 | 543 | 544 | @dataclass 545 | class OrganizationInvitesCreateRequest: 546 | invitations: list[OrganizationInviteCreateRequest] 547 | 548 | 549 | @dataclass 550 | class OrganizationInviteUpdateRequest: 551 | email: Optional[str] = None 552 | role: Optional[MemberRole] = None 553 | resend: Optional[bool] = None 554 | 555 | 556 | @dataclass 557 | class OrganizationGuest: 558 | permission_id: str 559 | user_email: str 560 | project_id: str 561 | project_name: str 562 | 563 | 564 | @dataclass 565 | class OrganizationMemberUpdateRequest: 566 | role: MemberRole 567 | 568 | 569 | @dataclass 570 | class OrganizationMembersResponse: 571 | members: list[MemberWithUser] 572 | 573 | 574 | @dataclass 575 | class InvitationCreateRequest: 576 | email: str 577 | role: MemberRole 578 | 579 | 580 | @dataclass 581 | class Organization1: 582 | name: Optional[str] = None 583 | invitations: Optional[list[InvitationCreateRequest]] = None 584 | 585 | 586 | @dataclass 587 | class OrganizationCreateRequest: 588 | organization: Organization1 589 | subscription_type: BillingSubscriptionType 590 | 591 | 592 | @dataclass 593 | class OrganizationLimits: 594 | limits: Limits 595 | features: dict[str, bool] 596 | 597 | 598 | @dataclass 599 | class RegionResponse: 600 | region_id: str 601 | name: str 602 | default: bool 603 | geo_lat: str 604 | geo_long: str 605 | 606 | 607 | @dataclass 608 | class UpdateUserInfoRequest: 609 | id: str 610 | email: Optional[str] = None 611 | image: Optional[str] = None 612 | first_name: Optional[str] = None 613 | last_name: Optional[str] = None 614 | password: Optional[str] = None 615 | new_password: Optional[str] = None 616 | 617 | 618 | @dataclass 619 | class ConvertUserToOrgRequest: 620 | name: str 621 | 622 | 623 | @dataclass 624 | class TransferProjectsToOrganizationRequest: 625 | org_id: str 626 | project_ids: list[str] 627 | 628 | 629 | @dataclass 630 | class VerifyUserPasswordRequest: 631 | password: str 632 | 633 | 634 | class IdentityProviderId(Enum): 635 | github = 'github' 636 | google = 'google' 637 | hasura = 'hasura' 638 | microsoft = 'microsoft' 639 | microsoftv2 = 'microsoftv2' 640 | vercelmp = 'vercelmp' 641 | keycloak = 'keycloak' 642 | test = 'test' 643 | 644 | 645 | @dataclass 646 | class ProjectQuota: 647 | active_time_seconds: Optional[int] = None 648 | compute_time_seconds: Optional[int] = None 649 | written_data_bytes: Optional[int] = None 650 | data_transfer_bytes: Optional[int] = None 651 | logical_size_bytes: Optional[int] = None 652 | 653 | 654 | @dataclass 655 | class HealthCheck: 656 | status: str 657 | 658 | 659 | @dataclass 660 | class ProjectOwnerData: 661 | email: str 662 | name: str 663 | branches_limit: int 664 | subscription_type: BillingSubscriptionType 665 | 666 | 667 | @dataclass 668 | class Limit: 669 | name: str 670 | expected: str 671 | actual: str 672 | 673 | 674 | @dataclass 675 | class LimitsUnsatisfiedResponse: 676 | limits: list[Limit] 677 | 678 | 679 | @dataclass 680 | class Project3: 681 | id: str 682 | integration: str 683 | 684 | 685 | @dataclass 686 | class ProjectsWithIntegrationResponse: 687 | projects: list[Project3] 688 | 689 | 690 | class UserDeletionConditionName(Enum): 691 | project_count = 'project_count' 692 | org_admin_membership_count = 'org_admin_membership_count' 693 | subscription_type = 'subscription_type' 694 | 695 | 696 | class OrgDeletionConditionName(Enum): 697 | project_count = 'project_count' 698 | 699 | 700 | class IdentitySupportedAuthProvider(Enum): 701 | mock = 'mock' 702 | stack = 'stack' 703 | 704 | 705 | @dataclass 706 | class IdentityIntegration: 707 | auth_provider: str 708 | auth_provider_project_id: str 709 | branch_id: str 710 | db_name: str 711 | created_at: str 712 | 713 | 714 | class SupportTicketSeverity(Enum): 715 | low = 'low' 716 | normal = 'normal' 717 | high = 'high' 718 | critical = 'critical' 719 | 720 | 721 | @dataclass 722 | class AnnotationObjectData: 723 | type: str 724 | id: str 725 | 726 | 727 | @dataclass 728 | class AnnotationCreateValueRequest: 729 | annotation_value: Optional[dict[str, str]] = None 730 | 731 | 732 | class Application(Enum): 733 | vercel = 'vercel' 734 | github = 'github' 735 | datadog = 'datadog' 736 | 737 | 738 | @dataclass 739 | class ProjectsApplicationsMapResponse: 740 | applications: dict[str, list[Application]] 741 | 742 | 743 | class Integration(Enum): 744 | vercel = 'vercel' 745 | github = 'github' 746 | datadog = 'datadog' 747 | 748 | 749 | @dataclass 750 | class ProjectsIntegrationsMapResponse: 751 | integrations: dict[str, list[Integration]] 752 | 753 | 754 | @dataclass 755 | class CursorPagination: 756 | next: Optional[str] = None 757 | previous: Optional[str] = None 758 | sort_by: Optional[str] = None 759 | sort_order: Optional[str] = None 760 | 761 | 762 | @dataclass 763 | class PaginationResponse: 764 | pagination: Optional[Pagination] = None 765 | 766 | 767 | @dataclass 768 | class ApiKeysListResponseItem: 769 | id: int 770 | name: str 771 | created_at: str 772 | created_by: ApiKeyCreatorData 773 | last_used_from_addr: str 774 | last_used_at: Optional[str] = None 775 | 776 | 777 | @dataclass 778 | class OrgApiKeysListResponseItem(ApiKeysListResponseItem): 779 | project_id: Optional[str] = None 780 | 781 | 782 | @dataclass 783 | class Operation: 784 | id: str 785 | project_id: str 786 | action: OperationAction 787 | status: OperationStatus 788 | failures_count: int 789 | created_at: str 790 | updated_at: str 791 | total_duration_ms: int 792 | branch_id: Optional[str] = None 793 | endpoint_id: Optional[str] = None 794 | error: Optional[str] = None 795 | retry_at: Optional[str] = None 796 | 797 | 798 | @dataclass 799 | class OperationResponse: 800 | operation: Operation 801 | 802 | 803 | @dataclass 804 | class OperationsResponse: 805 | operations: list[Operation] 806 | 807 | 808 | @dataclass 809 | class ProjectSettingsData: 810 | quota: Optional[ProjectQuota] = None 811 | allowed_ips: Optional[AllowedIps] = None 812 | enable_logical_replication: Optional[bool] = None 813 | maintenance_window: Optional[MaintenanceWindow] = None 814 | block_public_connections: Optional[bool] = None 815 | block_vpc_connections: Optional[bool] = None 816 | 817 | 818 | @dataclass 819 | class ConsumptionHistoryPerPeriod: 820 | period_id: str 821 | period_plan: str 822 | period_start: str 823 | consumption: list[ConsumptionHistoryPerTimeframe] 824 | period_end: Optional[str] = None 825 | 826 | 827 | @dataclass 828 | class ProjectLimits: 829 | limits: Limits 830 | features: dict[str, bool] 831 | 832 | 833 | @dataclass 834 | class Branch1: 835 | id: str 836 | project_id: str 837 | name: str 838 | current_state: str 839 | state_changed_at: str 840 | creation_source: str 841 | default: bool 842 | protected: bool 843 | cpu_used_sec: int 844 | compute_time_seconds: int 845 | active_time_seconds: int 846 | written_data_bytes: int 847 | data_transfer_bytes: int 848 | created_at: str 849 | updated_at: str 850 | parent_id: Optional[str] = None 851 | parent_lsn: Optional[str] = None 852 | parent_timestamp: Optional[str] = None 853 | pending_state: Optional[str] = None 854 | logical_size: Optional[int] = None 855 | primary: Optional[bool] = None 856 | last_reset_at: Optional[str] = None 857 | created_by: Optional[CreatedBy] = None 858 | 859 | 860 | @dataclass 861 | class BranchCreateRequestEndpointOptions: 862 | type: EndpointType 863 | autoscaling_limit_min_cu: Optional[float] = None 864 | autoscaling_limit_max_cu: Optional[float] = None 865 | provisioner: Optional[str] = None 866 | suspend_timeout_seconds: Optional[int] = None 867 | 868 | 869 | @dataclass 870 | class BranchCreateRequest: 871 | endpoints: Optional[list[BranchCreateRequestEndpointOptions]] = None 872 | branch: Optional[Branch2] = None 873 | 874 | 875 | @dataclass 876 | class BranchResponse: 877 | branch: Branch1 878 | 879 | 880 | @dataclass 881 | class BranchesResponse: 882 | branches: list[Branch1] 883 | 884 | 885 | @dataclass 886 | class VPCEndpointsResponse: 887 | endpoints: list[VPCEndpoint] 888 | 889 | 890 | @dataclass 891 | class StatementResult: 892 | query: str 893 | data: Optional[StatementData] = None 894 | error: Optional[str] = None 895 | explain_data: Optional[list[ExplainData]] = None 896 | 897 | 898 | @dataclass 899 | class BillingAccount: 900 | state: BillingAccountState 901 | payment_source: PaymentSource 902 | subscription_type: BillingSubscriptionType 903 | payment_method: BillingPaymentMethod 904 | quota_reset_at_last: str 905 | name: str 906 | email: str 907 | address_city: str 908 | address_country: str 909 | address_line1: str 910 | address_line2: str 911 | address_postal_code: str 912 | address_state: str 913 | address_country_name: Optional[str] = None 914 | orb_portal_url: Optional[str] = None 915 | tax_id: Optional[str] = None 916 | tax_id_type: Optional[str] = None 917 | 918 | 919 | @dataclass 920 | class Invitation: 921 | id: str 922 | email: str 923 | org_id: str 924 | invited_by: str 925 | invited_at: str 926 | role: MemberRole 927 | 928 | 929 | @dataclass 930 | class OrganizationInvitationsResponse: 931 | invitations: list[Invitation] 932 | 933 | 934 | OrganizationGuestsResponse = list[OrganizationGuest] 935 | 936 | 937 | @dataclass 938 | class ActiveRegionsResponse: 939 | regions: list[RegionResponse] 940 | 941 | 942 | @dataclass 943 | class CurrentUserAuthAccount: 944 | email: str 945 | image: str 946 | login: str 947 | name: str 948 | provider: IdentityProviderId 949 | 950 | 951 | @dataclass 952 | class LinkedAuthAccount: 953 | provider: IdentityProviderId 954 | provider_display_name: str 955 | username: str 956 | 957 | 958 | @dataclass 959 | class CurrentUserInfoResponse: 960 | active_seconds_limit: int 961 | billing_account: BillingAccount 962 | auth_accounts: list[CurrentUserAuthAccount] 963 | email: str 964 | id: str 965 | image: str 966 | login: str 967 | name: str 968 | last_name: str 969 | projects_limit: int 970 | branches_limit: int 971 | max_autoscaling_limit: float 972 | plan: str 973 | compute_seconds_limit: Optional[int] = None 974 | 975 | 976 | @dataclass 977 | class CurrentUserInfoAuthResponse: 978 | password_stored: bool 979 | auth_accounts: list[CurrentUserAuthAccount] 980 | linked_accounts: list[LinkedAuthAccount] 981 | provider: str 982 | 983 | 984 | @dataclass 985 | class EndpointSettingsData: 986 | pg_settings: Optional[dict[str, str]] = None 987 | pgbouncer_settings: Optional[dict[str, str]] = None 988 | 989 | 990 | @dataclass 991 | class DefaultEndpointSettings: 992 | pg_settings: Optional[dict[str, str]] = None 993 | pgbouncer_settings: Optional[dict[str, str]] = None 994 | autoscaling_limit_min_cu: Optional[float] = None 995 | autoscaling_limit_max_cu: Optional[float] = None 996 | suspend_timeout_seconds: Optional[int] = None 997 | 998 | 999 | @dataclass 1000 | class ListProjectIdentityIntegrationsResponse: 1001 | data: list[IdentityIntegration] 1002 | 1003 | 1004 | @dataclass 1005 | class GeneralError: 1006 | code: str 1007 | message: str 1008 | request_id: Optional[str] = None 1009 | 1010 | 1011 | @dataclass 1012 | class BranchOperations(BranchResponse, OperationsResponse): 1013 | pass 1014 | 1015 | 1016 | @dataclass 1017 | class DatabaseOperations(DatabaseResponse, OperationsResponse): 1018 | pass 1019 | 1020 | 1021 | @dataclass 1022 | class RoleOperations(RoleResponse, OperationsResponse): 1023 | pass 1024 | 1025 | 1026 | @dataclass 1027 | class JWKSCreationOperation(JWKSResponse, OperationsResponse): 1028 | pass 1029 | 1030 | 1031 | @dataclass 1032 | class AnnotationData: 1033 | object: AnnotationObjectData 1034 | value: dict[str, str] 1035 | created_at: Optional[str] = None 1036 | updated_at: Optional[str] = None 1037 | 1038 | 1039 | @dataclass 1040 | class AnnotationResponse: 1041 | annotation: AnnotationData 1042 | 1043 | 1044 | @dataclass 1045 | class AnnotationsMapResponse: 1046 | annotations: dict[str, AnnotationData] 1047 | 1048 | 1049 | @dataclass 1050 | class CursorPaginationResponse: 1051 | pagination: Optional[CursorPagination] = None 1052 | 1053 | 1054 | @dataclass 1055 | class ProjectListItem: 1056 | id: str 1057 | platform_id: str 1058 | region_id: str 1059 | name: str 1060 | provisioner: str 1061 | pg_version: int 1062 | proxy_host: str 1063 | branch_logical_size_limit: int 1064 | branch_logical_size_limit_bytes: int 1065 | store_passwords: bool 1066 | active_time: int 1067 | cpu_used_sec: int 1068 | creation_source: str 1069 | created_at: str 1070 | updated_at: str 1071 | owner_id: str 1072 | default_endpoint_settings: Optional[DefaultEndpointSettings] = None 1073 | settings: Optional[ProjectSettingsData] = None 1074 | maintenance_starts_at: Optional[str] = None 1075 | synthetic_storage_size: Optional[int] = None 1076 | quota_reset_at: Optional[str] = None 1077 | compute_last_active_at: Optional[str] = None 1078 | org_id: Optional[str] = None 1079 | 1080 | 1081 | @dataclass 1082 | class Project: 1083 | data_storage_bytes_hour: int 1084 | data_transfer_bytes: int 1085 | written_data_bytes: int 1086 | compute_time_seconds: int 1087 | active_time_seconds: int 1088 | cpu_used_sec: int 1089 | id: str 1090 | platform_id: str 1091 | region_id: str 1092 | name: str 1093 | provisioner: str 1094 | pg_version: int 1095 | proxy_host: str 1096 | branch_logical_size_limit: int 1097 | branch_logical_size_limit_bytes: int 1098 | store_passwords: bool 1099 | creation_source: str 1100 | history_retention_seconds: int 1101 | created_at: str 1102 | updated_at: str 1103 | consumption_period_start: str 1104 | consumption_period_end: str 1105 | owner_id: str 1106 | default_endpoint_settings: Optional[DefaultEndpointSettings] = None 1107 | settings: Optional[ProjectSettingsData] = None 1108 | maintenance_starts_at: Optional[str] = None 1109 | synthetic_storage_size: Optional[int] = None 1110 | quota_reset_at: Optional[str] = None 1111 | owner: Optional[ProjectOwnerData] = None 1112 | compute_last_active_at: Optional[str] = None 1113 | org_id: Optional[str] = None 1114 | 1115 | 1116 | @dataclass 1117 | class Project1: 1118 | settings: Optional[ProjectSettingsData] = None 1119 | name: Optional[str] = None 1120 | branch: Optional[Branch] = None 1121 | autoscaling_limit_min_cu: Optional[float] = None 1122 | autoscaling_limit_max_cu: Optional[float] = None 1123 | provisioner: Optional[str] = None 1124 | region_id: Optional[str] = None 1125 | default_endpoint_settings: Optional[DefaultEndpointSettings] = None 1126 | pg_version: Optional[int] = None 1127 | store_passwords: Optional[bool] = None 1128 | history_retention_seconds: Optional[int] = None 1129 | org_id: Optional[str] = None 1130 | 1131 | 1132 | @dataclass 1133 | class ProjectCreateRequest: 1134 | project: Project1 1135 | 1136 | 1137 | @dataclass 1138 | class Project2: 1139 | settings: Optional[ProjectSettingsData] = None 1140 | name: Optional[str] = None 1141 | default_endpoint_settings: Optional[DefaultEndpointSettings] = None 1142 | history_retention_seconds: Optional[int] = None 1143 | 1144 | 1145 | @dataclass 1146 | class ProjectUpdateRequest: 1147 | project: Project2 1148 | 1149 | 1150 | @dataclass 1151 | class ProjectResponse: 1152 | project: Project 1153 | 1154 | 1155 | @dataclass 1156 | class ProjectsResponse: 1157 | projects: list[ProjectListItem] 1158 | unavailable_project_ids: Optional[list[str]] = None 1159 | 1160 | 1161 | @dataclass 1162 | class ConsumptionHistoryPerAccountResponse: 1163 | periods: list[ConsumptionHistoryPerPeriod] 1164 | 1165 | 1166 | @dataclass 1167 | class ConsumptionHistoryPerProject: 1168 | project_id: str 1169 | periods: list[ConsumptionHistoryPerPeriod] 1170 | 1171 | 1172 | @dataclass 1173 | class Endpoint: 1174 | host: str 1175 | id: str 1176 | project_id: str 1177 | branch_id: str 1178 | autoscaling_limit_min_cu: float 1179 | autoscaling_limit_max_cu: float 1180 | region_id: str 1181 | type: EndpointType 1182 | current_state: EndpointState 1183 | settings: EndpointSettingsData 1184 | pooler_enabled: bool 1185 | pooler_mode: EndpointPoolerMode 1186 | disabled: bool 1187 | passwordless_access: bool 1188 | creation_source: str 1189 | created_at: str 1190 | updated_at: str 1191 | proxy_host: str 1192 | suspend_timeout_seconds: int 1193 | provisioner: str 1194 | pending_state: Optional[EndpointState] = None 1195 | last_active: Optional[str] = None 1196 | compute_release_version: Optional[str] = None 1197 | 1198 | 1199 | @dataclass 1200 | class Endpoint1: 1201 | branch_id: str 1202 | type: EndpointType 1203 | region_id: Optional[str] = None 1204 | settings: Optional[EndpointSettingsData] = None 1205 | autoscaling_limit_min_cu: Optional[float] = None 1206 | autoscaling_limit_max_cu: Optional[float] = None 1207 | provisioner: Optional[str] = None 1208 | pooler_enabled: Optional[bool] = None 1209 | pooler_mode: Optional[EndpointPoolerMode] = None 1210 | disabled: Optional[bool] = None 1211 | passwordless_access: Optional[bool] = None 1212 | suspend_timeout_seconds: Optional[int] = None 1213 | 1214 | 1215 | @dataclass 1216 | class EndpointCreateRequest: 1217 | endpoint: Endpoint1 1218 | 1219 | 1220 | @dataclass 1221 | class Endpoint2: 1222 | branch_id: Optional[str] = None 1223 | autoscaling_limit_min_cu: Optional[float] = None 1224 | autoscaling_limit_max_cu: Optional[float] = None 1225 | provisioner: Optional[str] = None 1226 | settings: Optional[EndpointSettingsData] = None 1227 | pooler_enabled: Optional[bool] = None 1228 | pooler_mode: Optional[EndpointPoolerMode] = None 1229 | disabled: Optional[bool] = None 1230 | passwordless_access: Optional[bool] = None 1231 | suspend_timeout_seconds: Optional[int] = None 1232 | 1233 | 1234 | @dataclass 1235 | class EndpointUpdateRequest: 1236 | endpoint: Endpoint2 1237 | 1238 | 1239 | @dataclass 1240 | class EndpointResponse: 1241 | endpoint: Endpoint 1242 | 1243 | 1244 | @dataclass 1245 | class EndpointsResponse: 1246 | endpoints: list[Endpoint] 1247 | 1248 | 1249 | @dataclass 1250 | class EndpointOperations(EndpointResponse, OperationsResponse): 1251 | pass 1252 | 1253 | 1254 | @dataclass 1255 | class ConsumptionHistoryPerProjectResponse: 1256 | projects: list[ConsumptionHistoryPerProject] 1257 | -------------------------------------------------------------------------------- /neon_api/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | def compact_mapping(obj): 5 | """Compact a dict/mapping by removing all None values.""" 6 | 7 | return {k: v for k, v in obj.items() if v is not None} 8 | 9 | 10 | def to_iso8601(dt): 11 | """Convert a datetime object to an 12 | `ISO 8601 `_ string. 13 | """ 14 | 15 | return dt.strftime("%Y-%m-%dT%H:%M:%SZ") 16 | 17 | 18 | def from_iso8601(s): 19 | """Convert an `ISO 8601 `_ 20 | string to a datetime object. 21 | """ 22 | 23 | return datetime.strptime(s, "%Y-%m-%dT%H:%M:%SZ") 24 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "neon-api" 3 | version = "0.3.0" 4 | description = "An API Client for the Neon API." 5 | authors = ["Kenneth Reitz "] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://github.com/neondatabase/neon-api-python" 9 | classifiers = [ 10 | "License :: OSI Approved :: Apache Software License", 11 | "Programming Language :: Python", 12 | "Programming Language :: Python :: 3", 13 | "Programming Language :: Python :: 3.9", 14 | "Programming Language :: Python :: 3.10", 15 | "Programming Language :: Python :: 3.11", 16 | "Programming Language :: Python :: 3.12", 17 | "Programming Language :: Python :: Implementation :: CPython", 18 | "Programming Language :: Python :: Implementation :: PyPy", 19 | ] 20 | 21 | [tool.poetry.dependencies] 22 | python = "^3.9" 23 | requests = "*" 24 | pydantic = ">=2.0.0" 25 | 26 | [tool.poetry.group.test.dependencies] 27 | datamodel-code-generator = "*" 28 | pytest = "*" 29 | pytest-cov = "*" 30 | pytest-ordering = "*" 31 | pytest-recording = "*" 32 | ruff = "*" 33 | sphinx = "*" 34 | renku-sphinx-theme = "*" 35 | 36 | 37 | [build-system] 38 | requires = ["poetry-core>=1.0.0"] 39 | build-backend = "poetry.core.masonry.api" 40 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | datamodel-code-generator 3 | pytest 4 | pytest-cov 5 | pytest-ordering 6 | pytest-recording 7 | ruff 8 | sphinx 9 | renku-sphinx-theme 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Note: To use the 'upload' functionality of this file, you must: 5 | # $ pipenv install twine --dev 6 | 7 | import io 8 | import os 9 | import sys 10 | from shutil import rmtree 11 | 12 | from setuptools import find_packages, setup, Command 13 | 14 | # Package meta-data. 15 | NAME = "neon-api" 16 | DESCRIPTION = "An API Client for the Neon API." 17 | URL = "https://github.com/neondatabase/neon-api-python" 18 | EMAIL = "me@kennethreitz.org" 19 | AUTHOR = "Kenneth Reitz" 20 | REQUIRES_PYTHON = ">=3.9.0" 21 | VERSION = "0.3.0" 22 | 23 | # What packages are required for this module to be executed? 24 | REQUIRED = ["requests", "pydantic >= 2.0.0"] 25 | 26 | # What packages are optional? 27 | EXTRAS = { 28 | # "tests": ["pytest"], 29 | } 30 | 31 | # The rest you shouldn't have to touch too much :) 32 | # ------------------------------------------------ 33 | # Except, perhaps the License and Trove Classifiers! 34 | # If you do change the License, remember to change the Trove Classifier for that! 35 | 36 | here = os.path.abspath(os.path.dirname(__file__)) 37 | 38 | # Import the README and use it as the long-description. 39 | # Note: this will only work if 'README.md' is present in your MANIFEST.in file! 40 | try: 41 | with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f: 42 | long_description = "\n" + f.read() 43 | except FileNotFoundError: 44 | long_description = DESCRIPTION 45 | 46 | # Load the package's __version__.py module as a dictionary. 47 | about = {} 48 | if not VERSION: 49 | project_slug = NAME.lower().replace("-", "_").replace(" ", "_") 50 | with open(os.path.join(here, project_slug, "__version__.py")) as f: 51 | exec(f.read(), about) 52 | else: 53 | about["__version__"] = VERSION 54 | 55 | 56 | class UploadCommand(Command): 57 | """Support setup.py upload.""" 58 | 59 | description = "Build and publish the package." 60 | user_options = [] 61 | 62 | @staticmethod 63 | def status(s): 64 | """Prints things in bold.""" 65 | print("\033[1m{0}\033[0m".format(s)) 66 | 67 | def initialize_options(self): 68 | pass 69 | 70 | def finalize_options(self): 71 | pass 72 | 73 | def run(self): 74 | try: 75 | self.status("Removing previous builds…") 76 | rmtree(os.path.join(here, "dist")) 77 | except OSError: 78 | pass 79 | 80 | self.status("Building Source and Wheel (universal) distribution…") 81 | os.system("{0} setup.py sdist bdist_wheel --universal".format(sys.executable)) 82 | 83 | self.status("Uploading the package to PyPI via Twine…") 84 | os.system("twine upload dist/*") 85 | 86 | self.status("Pushing git tags…") 87 | os.system("git tag v{0}".format(about["__version__"])) 88 | os.system("git push --tags") 89 | 90 | sys.exit() 91 | 92 | 93 | # Where the magic happens: 94 | setup( 95 | name=NAME, 96 | version=about["__version__"], 97 | description=DESCRIPTION, 98 | long_description=long_description, 99 | long_description_content_type="text/markdown", 100 | author=AUTHOR, 101 | author_email=EMAIL, 102 | python_requires=REQUIRES_PYTHON, 103 | url=URL, 104 | packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]), 105 | # If your package is a single module, use this instead of 'packages': 106 | # py_modules=['mypackage'], 107 | # entry_points={ 108 | # 'console_scripts': ['mycli=mymodule:cli'], 109 | # }, 110 | install_requires=REQUIRED, 111 | extras_require=EXTRAS, 112 | include_package_data=True, 113 | license="Apache 2.0", 114 | classifiers=[ 115 | # Trove classifiers 116 | # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 117 | "License :: OSI Approved :: Apache Software License", 118 | "Programming Language :: Python", 119 | "Programming Language :: Python :: 3", 120 | "Programming Language :: Python :: 3.9", 121 | "Programming Language :: Python :: 3.10", 122 | "Programming Language :: Python :: 3.11", 123 | "Programming Language :: Python :: 3.12", 124 | "Programming Language :: Python :: Implementation :: CPython", 125 | "Programming Language :: Python :: Implementation :: PyPy", 126 | ], 127 | # $ setup.py publish support. 128 | cmdclass={ 129 | "upload": UploadCommand, 130 | }, 131 | ) 132 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_api_keys.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: '{"key_name": "pytest-8050"}' 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Length: 12 | - '27' 13 | Content-Type: 14 | - application/json 15 | User-Agent: 16 | - neon-client/python version=(0.1.0) 17 | method: POST 18 | uri: https://console.neon.tech/api/v2/api_keys 19 | response: 20 | body: 21 | string: '{"id":1954948,"key":"m5azy6fs2cea9mr9b37e5k6wclu7olast37zrt3f1mjhha02gl8if38pb2tmdsot","name":"pytest-8050","created_at":"2025-01-16T21:01:58Z","created_by":"92cdd2ee-a6d8-4647-868f-124531123b03"}' 22 | headers: 23 | CF-Cache-Status: 24 | - DYNAMIC 25 | CF-RAY: 26 | - 90310b393875de9b-EWR 27 | Connection: 28 | - keep-alive 29 | Content-Encoding: 30 | - gzip 31 | Content-Type: 32 | - application/json; charset=utf-8 33 | Date: 34 | - Thu, 16 Jan 2025 21:01:58 GMT 35 | Server: 36 | - cloudflare 37 | Transfer-Encoding: 38 | - chunked 39 | referrer-policy: 40 | - strict-origin-when-cross-origin 41 | strict-transport-security: 42 | - max-age=63072000; includeSubDomains; preload 43 | vary: 44 | - Origin 45 | x-content-type-options: 46 | - nosniff 47 | x-frame-options: 48 | - SAMEORIGIN 49 | x-neon-ret-request-id: 50 | - 332164ee-24a6-440d-a486-77679f124fd4 51 | status: 52 | code: 200 53 | message: OK 54 | - request: 55 | body: null 56 | headers: 57 | Accept: 58 | - application/json 59 | Accept-Encoding: 60 | - gzip, deflate 61 | Connection: 62 | - keep-alive 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - neon-client/python version=(0.1.0) 67 | method: GET 68 | uri: https://console.neon.tech/api/v2/api_keys 69 | response: 70 | body: 71 | string: '[{"id":1954948,"name":"pytest-8050","created_at":"2025-01-16T21:01:58Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 72 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":null,"last_used_from_addr":""},{"id":1937557,"name":"other 73 | test","created_at":"2025-01-03T20:02:41Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 74 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":"2025-01-03T20:06:28Z","last_used_from_addr":"52.200.136.63"},{"id":1934773,"name":"quick_test","created_at":"2025-01-01T21:15:36Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 75 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":"2025-01-03T13:03:20Z","last_used_from_addr":"54.234.118.252"},{"id":1934772,"name":"composio","created_at":"2025-01-01T21:15:02Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 76 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":null,"last_used_from_addr":""},{"id":1885316,"name":"claude","created_at":"2024-12-02T08:07:54Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 77 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":null,"last_used_from_addr":""},{"id":1835348,"name":"another 78 | one","created_at":"2024-11-17T17:48:24Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 79 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":"2024-11-17T17:49:31Z","last_used_from_addr":"113.203.180.132,35.160.120.126"},{"id":1831722,"name":"agent","created_at":"2024-11-16T13:12:47Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 80 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":"2025-01-14T21:21:34Z","last_used_from_addr":"172.56.167.151"},{"id":1813799,"name":"python 81 | demo api key","created_at":"2024-11-10T16:44:30Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 82 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":"2025-01-16T21:01:58Z","last_used_from_addr":"172.254.226.90"},{"id":1633393,"name":"test","created_at":"2024-09-21T19:51:43Z","created_by":{"id":"92cdd2ee-a6d8-4647-868f-124531123b03","name":"David 83 | Gomes","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"},"last_used_at":"2024-09-22T06:13:16Z","last_used_from_addr":"113.203.180.132"}]' 84 | headers: 85 | CF-Cache-Status: 86 | - DYNAMIC 87 | CF-RAY: 88 | - 90310b3a988272b9-EWR 89 | Connection: 90 | - keep-alive 91 | Content-Encoding: 92 | - gzip 93 | Content-Type: 94 | - application/json; charset=utf-8 95 | Date: 96 | - Thu, 16 Jan 2025 21:01:58 GMT 97 | Server: 98 | - cloudflare 99 | Transfer-Encoding: 100 | - chunked 101 | referrer-policy: 102 | - strict-origin-when-cross-origin 103 | strict-transport-security: 104 | - max-age=63072000; includeSubDomains; preload 105 | vary: 106 | - Origin 107 | x-content-type-options: 108 | - nosniff 109 | x-frame-options: 110 | - SAMEORIGIN 111 | x-neon-ret-request-id: 112 | - 929237b6-fa5d-4afb-bd37-c394f4180323 113 | status: 114 | code: 200 115 | message: OK 116 | - request: 117 | body: null 118 | headers: 119 | Accept: 120 | - application/json 121 | Accept-Encoding: 122 | - gzip, deflate 123 | Connection: 124 | - keep-alive 125 | Content-Length: 126 | - '0' 127 | Content-Type: 128 | - application/json 129 | User-Agent: 130 | - neon-client/python version=(0.1.0) 131 | method: DELETE 132 | uri: https://console.neon.tech/api/v2/api_keys/1954948 133 | response: 134 | body: 135 | string: '{"id":1954948,"name":"pytest-8050","created_at":"2025-01-16T21:01:58Z","created_by":"92cdd2ee-a6d8-4647-868f-124531123b03","last_used_at":null,"last_used_from_addr":"","revoked":true}' 136 | headers: 137 | CF-Cache-Status: 138 | - DYNAMIC 139 | CF-RAY: 140 | - 90310b3bbc9d7292-EWR 141 | Connection: 142 | - keep-alive 143 | Content-Encoding: 144 | - gzip 145 | Content-Type: 146 | - application/json; charset=utf-8 147 | Date: 148 | - Thu, 16 Jan 2025 21:01:59 GMT 149 | Server: 150 | - cloudflare 151 | Transfer-Encoding: 152 | - chunked 153 | referrer-policy: 154 | - strict-origin-when-cross-origin 155 | strict-transport-security: 156 | - max-age=63072000; includeSubDomains; preload 157 | vary: 158 | - Origin 159 | x-content-type-options: 160 | - nosniff 161 | x-frame-options: 162 | - SAMEORIGIN 163 | x-neon-ret-request-id: 164 | - fb342a59-a74a-47ce-a1da-46f8d6d1d2a1 165 | status: 166 | code: 200 167 | message: OK 168 | version: 1 169 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_api_keys_crud.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: '{"key_name": "pytest-260"}' 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Length: 12 | - '26' 13 | Content-Type: 14 | - application/json 15 | User-Agent: 16 | - neon-client/python version=(0.1.0) 17 | method: POST 18 | uri: https://console.neon.tech/api/v2/api_keys 19 | response: 20 | body: 21 | string: '{"id":768660,"key":"2kqtmmim7jh14cu7xq3cyxlje8k64sz4jq0j35auru81gba1utg2lphr5vui3j1t","name":"pytest-260","created_at":"2024-01-23T22:05:48Z"}' 22 | headers: 23 | Connection: 24 | - keep-alive 25 | Content-Length: 26 | - '142' 27 | Content-Type: 28 | - application/json 29 | Date: 30 | - Tue, 23 Jan 2024 22:05:48 GMT 31 | Strict-Transport-Security: 32 | - max-age=15724800; includeSubDomains 33 | Vary: 34 | - Origin 35 | X-Neon-Ret-Request-Id: 36 | - 06cb979ba61f7915ace82fefef0bd2f9 37 | status: 38 | code: 200 39 | message: OK 40 | - request: 41 | body: null 42 | headers: 43 | Accept: 44 | - application/json 45 | Accept-Encoding: 46 | - gzip, deflate 47 | Connection: 48 | - keep-alive 49 | Content-Length: 50 | - '0' 51 | Content-Type: 52 | - application/json 53 | User-Agent: 54 | - neon-client/python version=(0.1.0) 55 | method: DELETE 56 | uri: https://console.neon.tech/api/v2/api_keys/768660 57 | response: 58 | body: 59 | string: '{"id":768660,"name":"pytest-260","revoked":true,"last_used_at":null,"last_used_from_addr":""}' 60 | headers: 61 | Connection: 62 | - keep-alive 63 | Content-Length: 64 | - '93' 65 | Content-Type: 66 | - application/json 67 | Date: 68 | - Tue, 23 Jan 2024 22:05:48 GMT 69 | Strict-Transport-Security: 70 | - max-age=15724800; includeSubDomains 71 | Vary: 72 | - Origin 73 | X-Neon-Ret-Request-Id: 74 | - 679f5a7704fe0142235c329619c79c58 75 | status: 76 | code: 200 77 | message: OK 78 | version: 1 79 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_branches.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[],"applications":{},"integrations":{}}' 20 | headers: 21 | CF-Cache-Status: 22 | - DYNAMIC 23 | CF-RAY: 24 | - 90310b4c2b2e41db-EWR 25 | Connection: 26 | - keep-alive 27 | Content-Encoding: 28 | - gzip 29 | Content-Type: 30 | - application/json; charset=utf-8 31 | Date: 32 | - Thu, 16 Jan 2025 21:02:01 GMT 33 | Server: 34 | - cloudflare 35 | Transfer-Encoding: 36 | - chunked 37 | referrer-policy: 38 | - strict-origin-when-cross-origin 39 | strict-transport-security: 40 | - max-age=63072000; includeSubDomains; preload 41 | vary: 42 | - Origin 43 | x-content-type-options: 44 | - nosniff 45 | x-frame-options: 46 | - SAMEORIGIN 47 | x-neon-ret-request-id: 48 | - 9178553b-b252-4539-8cd6-21bf0a3742a7 49 | status: 50 | code: 200 51 | message: OK 52 | - request: 53 | body: '{"project": {"name": "pytest-7972"}}' 54 | headers: 55 | Accept: 56 | - application/json 57 | Accept-Encoding: 58 | - gzip, deflate 59 | Connection: 60 | - keep-alive 61 | Content-Length: 62 | - '36' 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - neon-client/python version=(0.1.0) 67 | method: POST 68 | uri: https://console.neon.tech/api/v2/projects 69 | response: 70 | body: 71 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"old-violet-32930710","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-7972","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z","consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"},"connection_uris":[{"connection_uri":"postgresql://neondb_owner:rR84YwkouSPQ@ep-misty-sea-a5833nbi.us-east-2.aws.neon.tech/neondb?sslmode=require","connection_parameters":{"database":"neondb","password":"rR84YwkouSPQ","role":"neondb_owner","host":"ep-misty-sea-a5833nbi.us-east-2.aws.neon.tech","pooler_host":"ep-misty-sea-a5833nbi-pooler.us-east-2.aws.neon.tech"}}],"roles":[{"branch_id":"br-noisy-breeze-a5e59rrb","name":"neondb_owner","password":"rR84YwkouSPQ","protected":false,"created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z"}],"databases":[{"id":39724308,"branch_id":"br-noisy-breeze-a5e59rrb","name":"neondb","owner_name":"neondb_owner","created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z"}],"operations":[{"id":"1e604957-513d-4930-86b3-443f34fe4d39","project_id":"old-violet-32930710","branch_id":"br-noisy-breeze-a5e59rrb","action":"create_timeline","status":"running","failures_count":0,"created_at":"2025-01-16T21:02:02Z","updated_at":"2025-01-16T21:02:02Z","total_duration_ms":0},{"id":"901af6fb-8692-43f4-82b6-6c701cba8ae9","project_id":"old-violet-32930710","branch_id":"br-noisy-breeze-a5e59rrb","endpoint_id":"ep-misty-sea-a5833nbi","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:02Z","updated_at":"2025-01-16T21:02:02Z","total_duration_ms":0}],"branch":{"id":"br-noisy-breeze-a5e59rrb","project_id":"old-violet-32930710","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:01Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z"},"endpoints":[{"host":"ep-misty-sea-a5833nbi.us-east-2.aws.neon.tech","id":"ep-misty-sea-a5833nbi","project_id":"old-violet-32930710","branch_id":"br-noisy-breeze-a5e59rrb","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"creation_source":"console","created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 72 | headers: 73 | CF-Cache-Status: 74 | - DYNAMIC 75 | CF-RAY: 76 | - 90310b4d5b9442ee-EWR 77 | Connection: 78 | - keep-alive 79 | Content-Length: 80 | - '3435' 81 | Content-Type: 82 | - application/json; charset=utf-8 83 | Date: 84 | - Thu, 16 Jan 2025 21:02:02 GMT 85 | Server: 86 | - cloudflare 87 | referrer-policy: 88 | - strict-origin-when-cross-origin 89 | strict-transport-security: 90 | - max-age=63072000; includeSubDomains; preload 91 | vary: 92 | - Origin 93 | x-content-type-options: 94 | - nosniff 95 | x-frame-options: 96 | - SAMEORIGIN 97 | x-neon-ret-request-id: 98 | - 8861a536-bb51-4013-ba0a-23dd2d4dcd70 99 | status: 100 | code: 201 101 | message: Created 102 | - request: 103 | body: null 104 | headers: 105 | Accept: 106 | - application/json 107 | Accept-Encoding: 108 | - gzip, deflate 109 | Connection: 110 | - keep-alive 111 | Content-Type: 112 | - application/json 113 | User-Agent: 114 | - neon-client/python version=(0.1.0) 115 | method: GET 116 | uri: https://console.neon.tech/api/v2/projects/old-violet-32930710/branches 117 | response: 118 | body: 119 | string: '{"branches":[{"id":"br-noisy-breeze-a5e59rrb","project_id":"old-violet-32930710","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:01Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z","created_by":{"name":"David","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"}}],"annotations":{},"pagination":{"next":"eyJicmFuY2hfaWQiOiJici1ub2lzeS1icmVlemUtYTVlNTlycmIiLCJzb3J0X2J5IjoidXBkYXRlZF9hdCIsInNvcnRfYnlfdmFsdWUiOiIyMDI1LTAxLTE2VDIxOjAyOjAxWiIsInNvcnRfb3JkZXIiOiJERVNDIn0=","sort_by":"updated_at","sort_order":"DESC"}}' 120 | headers: 121 | CF-Cache-Status: 122 | - DYNAMIC 123 | CF-RAY: 124 | - 90310b4faf67440b-EWR 125 | Connection: 126 | - keep-alive 127 | Content-Encoding: 128 | - gzip 129 | Content-Type: 130 | - application/json; charset=utf-8 131 | Date: 132 | - Thu, 16 Jan 2025 21:02:02 GMT 133 | Server: 134 | - cloudflare 135 | Transfer-Encoding: 136 | - chunked 137 | referrer-policy: 138 | - strict-origin-when-cross-origin 139 | strict-transport-security: 140 | - max-age=63072000; includeSubDomains; preload 141 | vary: 142 | - Origin 143 | x-content-type-options: 144 | - nosniff 145 | x-frame-options: 146 | - SAMEORIGIN 147 | x-neon-ret-request-id: 148 | - eae33650-bad1-4155-87fc-068330b05895 149 | status: 150 | code: 200 151 | message: OK 152 | version: 1 153 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_create_project.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[{"id":"yellow-bar-04969948","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-852","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0,"autoscaling_limit_max_cu":0,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"primary_branch_only":false},"enable_logical_replication":false},"pg_version":15,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":3072,"branch_logical_size_limit_bytes":3221225472,"store_passwords":true,"active_time":0,"cpu_used_sec":0,"creation_source":"console","created_at":"2024-01-23T22:24:44Z","updated_at":"2024-01-23T22:24:50Z","synthetic_storage_size":0,"quota_reset_at":"2024-02-01T00:00:00Z","owner_id":"838386f6-b5f1-4c3b-89a2-4f5a130ef3de"}],"pagination":{"cursor":"yellow-bar-04969948"}}' 20 | headers: 21 | Connection: 22 | - keep-alive 23 | Content-Length: 24 | - '798' 25 | Content-Type: 26 | - application/json 27 | Date: 28 | - Tue, 23 Jan 2024 22:26:03 GMT 29 | Strict-Transport-Security: 30 | - max-age=15724800; includeSubDomains 31 | Vary: 32 | - Origin 33 | X-Neon-Ret-Request-Id: 34 | - 7f400c82ef94c07c9e1270607f54873c 35 | status: 36 | code: 200 37 | message: OK 38 | - request: 39 | body: null 40 | headers: 41 | Accept: 42 | - application/json 43 | Accept-Encoding: 44 | - gzip, deflate 45 | Connection: 46 | - keep-alive 47 | Content-Length: 48 | - '0' 49 | Content-Type: 50 | - application/json 51 | User-Agent: 52 | - neon-client/python version=(0.1.0) 53 | method: DELETE 54 | uri: https://console.neon.tech/api/v2/projects/yellow-bar-04969948 55 | response: 56 | body: 57 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"yellow-bar-04969948","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-852","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"primary_branch_only":false},"enable_logical_replication":false},"pg_version":15,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":3072,"branch_logical_size_limit_bytes":3221225472,"store_passwords":true,"creation_source":"console","history_retention_seconds":604800,"created_at":"2024-01-23T22:24:44Z","updated_at":"2024-01-23T22:24:50Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"838386f6-b5f1-4c3b-89a2-4f5a130ef3de"}}' 58 | headers: 59 | Connection: 60 | - keep-alive 61 | Content-Length: 62 | - '965' 63 | Content-Type: 64 | - application/json 65 | Date: 66 | - Tue, 23 Jan 2024 22:26:03 GMT 67 | Strict-Transport-Security: 68 | - max-age=15724800; includeSubDomains 69 | Vary: 70 | - Origin 71 | X-Neon-Ret-Request-Id: 72 | - db7bf26ea50193f792ad222fb26e33f8 73 | status: 74 | code: 200 75 | message: OK 76 | - request: 77 | body: '{"project": {"name": "pytest-148"}}' 78 | headers: 79 | Accept: 80 | - application/json 81 | Accept-Encoding: 82 | - gzip, deflate 83 | Connection: 84 | - keep-alive 85 | Content-Length: 86 | - '35' 87 | Content-Type: 88 | - application/json 89 | User-Agent: 90 | - neon-client/python version=(0.1.0) 91 | method: POST 92 | uri: https://console.neon.tech/api/v2/projects 93 | response: 94 | body: 95 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"cold-bonus-61650555","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-148","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"primary_branch_only":false},"enable_logical_replication":false},"pg_version":15,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":3072,"branch_logical_size_limit_bytes":3221225472,"store_passwords":true,"creation_source":"console","history_retention_seconds":604800,"created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z","consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"838386f6-b5f1-4c3b-89a2-4f5a130ef3de"},"connection_uris":[{"connection_uri":"postgres://kennethreitz:Ke98dQHnJOcA@ep-delicate-glitter-a53ow19g.us-east-2.aws.neon.tech/neondb","connection_parameters":{"database":"neondb","password":"Ke98dQHnJOcA","role":"kennethreitz","host":"ep-delicate-glitter-a53ow19g.us-east-2.aws.neon.tech","pooler_host":"ep-delicate-glitter-a53ow19g-pooler.us-east-2.aws.neon.tech"}}],"roles":[{"branch_id":"br-raspy-sound-a54diuwm","name":"kennethreitz","password":"Ke98dQHnJOcA","protected":false,"created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z"}],"databases":[{"id":32625886,"branch_id":"br-raspy-sound-a54diuwm","name":"neondb","owner_name":"kennethreitz","created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z"}],"operations":[{"id":"7fda088f-8f28-41c2-9700-085b6d8e5378","project_id":"cold-bonus-61650555","branch_id":"br-raspy-sound-a54diuwm","action":"create_timeline","status":"running","failures_count":0,"created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z","total_duration_ms":0},{"id":"c8e18275-6549-4dae-bac6-64ffa5589e9b","project_id":"cold-bonus-61650555","branch_id":"br-raspy-sound-a54diuwm","endpoint_id":"ep-delicate-glitter-a53ow19g","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z","total_duration_ms":0}],"branch":{"id":"br-raspy-sound-a54diuwm","project_id":"cold-bonus-61650555","name":"main","current_state":"init","pending_state":"ready","creation_source":"console","primary":true,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z"},"endpoints":[{"host":"ep-delicate-glitter-a53ow19g.us-east-2.aws.neon.tech","id":"ep-delicate-glitter-a53ow19g","project_id":"cold-bonus-61650555","branch_id":"br-raspy-sound-a54diuwm","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"creation_source":"console","created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 96 | headers: 97 | Connection: 98 | - keep-alive 99 | Content-Type: 100 | - application/json 101 | Date: 102 | - Tue, 23 Jan 2024 22:26:03 GMT 103 | Strict-Transport-Security: 104 | - max-age=15724800; includeSubDomains 105 | Transfer-Encoding: 106 | - chunked 107 | Vary: 108 | - Origin 109 | X-Neon-Ret-Request-Id: 110 | - dee16f249e57ca07d14194d02e29e051 111 | status: 112 | code: 201 113 | message: Created 114 | - request: 115 | body: null 116 | headers: 117 | Accept: 118 | - application/json 119 | Accept-Encoding: 120 | - gzip, deflate 121 | Connection: 122 | - keep-alive 123 | Content-Length: 124 | - '0' 125 | Content-Type: 126 | - application/json 127 | User-Agent: 128 | - neon-client/python version=(0.1.0) 129 | method: DELETE 130 | uri: https://console.neon.tech/api/v2/projects/cold-bonus-61650555 131 | response: 132 | body: 133 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"cold-bonus-61650555","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-148","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"primary_branch_only":false},"enable_logical_replication":false},"pg_version":15,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":3072,"branch_logical_size_limit_bytes":3221225472,"store_passwords":true,"creation_source":"console","history_retention_seconds":604800,"created_at":"2024-01-23T22:26:03Z","updated_at":"2024-01-23T22:26:03Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"838386f6-b5f1-4c3b-89a2-4f5a130ef3de"}}' 134 | headers: 135 | Connection: 136 | - keep-alive 137 | Content-Length: 138 | - '965' 139 | Content-Type: 140 | - application/json 141 | Date: 142 | - Tue, 23 Jan 2024 22:26:03 GMT 143 | Strict-Transport-Security: 144 | - max-age=15724800; includeSubDomains 145 | Vary: 146 | - Origin 147 | X-Neon-Ret-Request-Id: 148 | - 1cbe267907ecfafe2d806b68efe399d5 149 | status: 150 | code: 200 151 | message: OK 152 | version: 1 153 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_database.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[{"id":"old-violet-32930710","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-7972","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"active_time":0,"cpu_used_sec":0,"creation_source":"console","created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z","synthetic_storage_size":0,"quota_reset_at":"2025-02-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}],"pagination":{"cursor":"old-violet-32930710"},"applications":{},"integrations":{}}' 20 | headers: 21 | CF-Cache-Status: 22 | - DYNAMIC 23 | CF-RAY: 24 | - 90310b50c939199d-EWR 25 | Connection: 26 | - keep-alive 27 | Content-Encoding: 28 | - gzip 29 | Content-Type: 30 | - application/json; charset=utf-8 31 | Date: 32 | - Thu, 16 Jan 2025 21:02:02 GMT 33 | Server: 34 | - cloudflare 35 | Transfer-Encoding: 36 | - chunked 37 | referrer-policy: 38 | - strict-origin-when-cross-origin 39 | strict-transport-security: 40 | - max-age=63072000; includeSubDomains; preload 41 | vary: 42 | - Origin 43 | x-content-type-options: 44 | - nosniff 45 | x-frame-options: 46 | - SAMEORIGIN 47 | x-neon-ret-request-id: 48 | - 9841aa78-5a4e-4fe3-8a31-73198af0a5ae 49 | status: 50 | code: 200 51 | message: OK 52 | - request: 53 | body: null 54 | headers: 55 | Accept: 56 | - application/json 57 | Accept-Encoding: 58 | - gzip, deflate 59 | Connection: 60 | - keep-alive 61 | Content-Length: 62 | - '0' 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - neon-client/python version=(0.1.0) 67 | method: DELETE 68 | uri: https://console.neon.tech/api/v2/projects/old-violet-32930710 69 | response: 70 | body: 71 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"old-violet-32930710","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-7972","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:01Z","updated_at":"2025-01-16T21:02:01Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}}' 72 | headers: 73 | CF-Cache-Status: 74 | - DYNAMIC 75 | CF-RAY: 76 | - 90310b525c444241-EWR 77 | Connection: 78 | - keep-alive 79 | Content-Encoding: 80 | - gzip 81 | Content-Type: 82 | - application/json; charset=utf-8 83 | Date: 84 | - Thu, 16 Jan 2025 21:02:02 GMT 85 | Server: 86 | - cloudflare 87 | Transfer-Encoding: 88 | - chunked 89 | referrer-policy: 90 | - strict-origin-when-cross-origin 91 | strict-transport-security: 92 | - max-age=63072000; includeSubDomains; preload 93 | vary: 94 | - Origin 95 | x-content-type-options: 96 | - nosniff 97 | x-frame-options: 98 | - SAMEORIGIN 99 | x-neon-ret-request-id: 100 | - 870fc99f-e4d5-4879-a6ce-3ad20948c973 101 | status: 102 | code: 200 103 | message: OK 104 | - request: 105 | body: '{"project": {"name": "pytest-5484"}}' 106 | headers: 107 | Accept: 108 | - application/json 109 | Accept-Encoding: 110 | - gzip, deflate 111 | Connection: 112 | - keep-alive 113 | Content-Length: 114 | - '36' 115 | Content-Type: 116 | - application/json 117 | User-Agent: 118 | - neon-client/python version=(0.1.0) 119 | method: POST 120 | uri: https://console.neon.tech/api/v2/projects 121 | response: 122 | body: 123 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"dry-hall-87167252","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-5484","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z","consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"},"connection_uris":[{"connection_uri":"postgresql://neondb_owner:tj7PZSJhFw5R@ep-tiny-silence-a53n6kux.us-east-2.aws.neon.tech/neondb?sslmode=require","connection_parameters":{"database":"neondb","password":"tj7PZSJhFw5R","role":"neondb_owner","host":"ep-tiny-silence-a53n6kux.us-east-2.aws.neon.tech","pooler_host":"ep-tiny-silence-a53n6kux-pooler.us-east-2.aws.neon.tech"}}],"roles":[{"branch_id":"br-spring-rice-a5g82f1f","name":"neondb_owner","password":"tj7PZSJhFw5R","protected":false,"created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z"}],"databases":[{"id":39724309,"branch_id":"br-spring-rice-a5g82f1f","name":"neondb","owner_name":"neondb_owner","created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z"}],"operations":[{"id":"10c9b5cf-e781-44b0-9b21-18d5c23be614","project_id":"dry-hall-87167252","branch_id":"br-spring-rice-a5g82f1f","action":"create_timeline","status":"running","failures_count":0,"created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z","total_duration_ms":0},{"id":"2f66df72-e055-4de9-bb6e-ec875c175e73","project_id":"dry-hall-87167252","branch_id":"br-spring-rice-a5g82f1f","endpoint_id":"ep-tiny-silence-a53n6kux","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z","total_duration_ms":0}],"branch":{"id":"br-spring-rice-a5g82f1f","project_id":"dry-hall-87167252","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:03Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z"},"endpoints":[{"host":"ep-tiny-silence-a53n6kux.us-east-2.aws.neon.tech","id":"ep-tiny-silence-a53n6kux","project_id":"dry-hall-87167252","branch_id":"br-spring-rice-a5g82f1f","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"creation_source":"console","created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 124 | headers: 125 | CF-Cache-Status: 126 | - DYNAMIC 127 | CF-RAY: 128 | - 90310b541d0c5e7f-EWR 129 | Connection: 130 | - keep-alive 131 | Content-Length: 132 | - '3437' 133 | Content-Type: 134 | - application/json; charset=utf-8 135 | Date: 136 | - Thu, 16 Jan 2025 21:02:03 GMT 137 | Server: 138 | - cloudflare 139 | referrer-policy: 140 | - strict-origin-when-cross-origin 141 | strict-transport-security: 142 | - max-age=63072000; includeSubDomains; preload 143 | vary: 144 | - Origin 145 | x-content-type-options: 146 | - nosniff 147 | x-frame-options: 148 | - SAMEORIGIN 149 | x-neon-ret-request-id: 150 | - 6b658abd-d6fb-4325-9826-7f84aecfb4fd 151 | status: 152 | code: 201 153 | message: Created 154 | - request: 155 | body: null 156 | headers: 157 | Accept: 158 | - application/json 159 | Accept-Encoding: 160 | - gzip, deflate 161 | Connection: 162 | - keep-alive 163 | Content-Type: 164 | - application/json 165 | User-Agent: 166 | - neon-client/python version=(0.1.0) 167 | method: GET 168 | uri: https://console.neon.tech/api/v2/projects/dry-hall-87167252/branches 169 | response: 170 | body: 171 | string: '{"branches":[{"id":"br-spring-rice-a5g82f1f","project_id":"dry-hall-87167252","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:03Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z","created_by":{"name":"David","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"}}],"annotations":{},"pagination":{"next":"eyJicmFuY2hfaWQiOiJici1zcHJpbmctcmljZS1hNWc4MmYxZiIsInNvcnRfYnkiOiJ1cGRhdGVkX2F0Iiwic29ydF9ieV92YWx1ZSI6IjIwMjUtMDEtMTZUMjE6MDI6MDNaIiwic29ydF9vcmRlciI6IkRFU0MifQ==","sort_by":"updated_at","sort_order":"DESC"}}' 172 | headers: 173 | CF-Cache-Status: 174 | - DYNAMIC 175 | CF-RAY: 176 | - 90310b560cf61a17-EWR 177 | Connection: 178 | - keep-alive 179 | Content-Encoding: 180 | - gzip 181 | Content-Type: 182 | - application/json; charset=utf-8 183 | Date: 184 | - Thu, 16 Jan 2025 21:02:03 GMT 185 | Server: 186 | - cloudflare 187 | Transfer-Encoding: 188 | - chunked 189 | referrer-policy: 190 | - strict-origin-when-cross-origin 191 | strict-transport-security: 192 | - max-age=63072000; includeSubDomains; preload 193 | vary: 194 | - Origin 195 | x-content-type-options: 196 | - nosniff 197 | x-frame-options: 198 | - SAMEORIGIN 199 | x-neon-ret-request-id: 200 | - d9636f2a-ef12-4d41-a106-a844142ae063 201 | status: 202 | code: 200 203 | message: OK 204 | - request: 205 | body: null 206 | headers: 207 | Accept: 208 | - application/json 209 | Accept-Encoding: 210 | - gzip, deflate 211 | Connection: 212 | - keep-alive 213 | Content-Type: 214 | - application/json 215 | User-Agent: 216 | - neon-client/python version=(0.1.0) 217 | method: GET 218 | uri: https://console.neon.tech/api/v2/projects/dry-hall-87167252/branches/br-spring-rice-a5g82f1f/databases 219 | response: 220 | body: 221 | string: '{"databases":[{"id":39724309,"branch_id":"br-spring-rice-a5g82f1f","name":"neondb","owner_name":"neondb_owner","created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z"}]}' 222 | headers: 223 | CF-Cache-Status: 224 | - DYNAMIC 225 | CF-RAY: 226 | - 90310b573eb44390-EWR 227 | Connection: 228 | - keep-alive 229 | Content-Encoding: 230 | - gzip 231 | Content-Type: 232 | - application/json; charset=utf-8 233 | Date: 234 | - Thu, 16 Jan 2025 21:02:03 GMT 235 | Server: 236 | - cloudflare 237 | Transfer-Encoding: 238 | - chunked 239 | referrer-policy: 240 | - strict-origin-when-cross-origin 241 | strict-transport-security: 242 | - max-age=63072000; includeSubDomains; preload 243 | vary: 244 | - Origin 245 | x-content-type-options: 246 | - nosniff 247 | x-frame-options: 248 | - SAMEORIGIN 249 | x-neon-ret-request-id: 250 | - 88015758-dec7-49da-bae2-374def81928a 251 | status: 252 | code: 200 253 | message: OK 254 | version: 1 255 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_endpoints.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[{"id":"solitary-cell-58208823","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-9154","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"active_time":0,"cpu_used_sec":0,"creation_source":"console","created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","synthetic_storage_size":0,"quota_reset_at":"2025-02-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}],"pagination":{"cursor":"solitary-cell-58208823"},"applications":{},"integrations":{}}' 20 | headers: 21 | CF-Cache-Status: 22 | - DYNAMIC 23 | CF-RAY: 24 | - 90310b61ddff72a7-EWR 25 | Connection: 26 | - keep-alive 27 | Content-Encoding: 28 | - gzip 29 | Content-Type: 30 | - application/json; charset=utf-8 31 | Date: 32 | - Thu, 16 Jan 2025 21:02:05 GMT 33 | Server: 34 | - cloudflare 35 | Transfer-Encoding: 36 | - chunked 37 | referrer-policy: 38 | - strict-origin-when-cross-origin 39 | strict-transport-security: 40 | - max-age=63072000; includeSubDomains; preload 41 | vary: 42 | - Origin 43 | x-content-type-options: 44 | - nosniff 45 | x-frame-options: 46 | - SAMEORIGIN 47 | x-neon-ret-request-id: 48 | - fd26cd63-f012-4f34-a32c-505fb94616ce 49 | status: 50 | code: 200 51 | message: OK 52 | - request: 53 | body: null 54 | headers: 55 | Accept: 56 | - application/json 57 | Accept-Encoding: 58 | - gzip, deflate 59 | Connection: 60 | - keep-alive 61 | Content-Length: 62 | - '0' 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - neon-client/python version=(0.1.0) 67 | method: DELETE 68 | uri: https://console.neon.tech/api/v2/projects/solitary-cell-58208823 69 | response: 70 | body: 71 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"solitary-cell-58208823","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-9154","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}}' 72 | headers: 73 | CF-Cache-Status: 74 | - DYNAMIC 75 | CF-RAY: 76 | - 90310b635e3b427c-EWR 77 | Connection: 78 | - keep-alive 79 | Content-Encoding: 80 | - gzip 81 | Content-Type: 82 | - application/json; charset=utf-8 83 | Date: 84 | - Thu, 16 Jan 2025 21:02:05 GMT 85 | Server: 86 | - cloudflare 87 | Transfer-Encoding: 88 | - chunked 89 | referrer-policy: 90 | - strict-origin-when-cross-origin 91 | strict-transport-security: 92 | - max-age=63072000; includeSubDomains; preload 93 | vary: 94 | - Origin 95 | x-content-type-options: 96 | - nosniff 97 | x-frame-options: 98 | - SAMEORIGIN 99 | x-neon-ret-request-id: 100 | - 327a5acd-39bd-4147-b28a-9e7653f8aded 101 | status: 102 | code: 200 103 | message: OK 104 | - request: 105 | body: '{"project": {"name": "pytest-2420"}}' 106 | headers: 107 | Accept: 108 | - application/json 109 | Accept-Encoding: 110 | - gzip, deflate 111 | Connection: 112 | - keep-alive 113 | Content-Length: 114 | - '36' 115 | Content-Type: 116 | - application/json 117 | User-Agent: 118 | - neon-client/python version=(0.1.0) 119 | method: POST 120 | uri: https://console.neon.tech/api/v2/projects 121 | response: 122 | body: 123 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"solitary-thunder-08372287","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-2420","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"},"connection_uris":[{"connection_uri":"postgresql://neondb_owner:bptXao8qK5Mi@ep-dawn-king-a527wlyu.us-east-2.aws.neon.tech/neondb?sslmode=require","connection_parameters":{"database":"neondb","password":"bptXao8qK5Mi","role":"neondb_owner","host":"ep-dawn-king-a527wlyu.us-east-2.aws.neon.tech","pooler_host":"ep-dawn-king-a527wlyu-pooler.us-east-2.aws.neon.tech"}}],"roles":[{"branch_id":"br-calm-union-a59jz26r","name":"neondb_owner","password":"bptXao8qK5Mi","protected":false,"created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z"}],"databases":[{"id":39724311,"branch_id":"br-calm-union-a59jz26r","name":"neondb","owner_name":"neondb_owner","created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z"}],"operations":[{"id":"586b409f-68ba-4615-a850-88a676bb6f9f","project_id":"solitary-thunder-08372287","branch_id":"br-calm-union-a59jz26r","action":"create_timeline","status":"running","failures_count":0,"created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","total_duration_ms":0},{"id":"5d5ecd28-161d-4ab0-bd5a-9a1cb3e3736e","project_id":"solitary-thunder-08372287","branch_id":"br-calm-union-a59jz26r","endpoint_id":"ep-dawn-king-a527wlyu","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","total_duration_ms":0}],"branch":{"id":"br-calm-union-a59jz26r","project_id":"solitary-thunder-08372287","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:05Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z"},"endpoints":[{"host":"ep-dawn-king-a527wlyu.us-east-2.aws.neon.tech","id":"ep-dawn-king-a527wlyu","project_id":"solitary-thunder-08372287","branch_id":"br-calm-union-a59jz26r","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"creation_source":"console","created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 124 | headers: 125 | CF-Cache-Status: 126 | - DYNAMIC 127 | CF-RAY: 128 | - 90310b64efa74288-EWR 129 | Connection: 130 | - keep-alive 131 | Content-Length: 132 | - '3453' 133 | Content-Type: 134 | - application/json; charset=utf-8 135 | Date: 136 | - Thu, 16 Jan 2025 21:02:05 GMT 137 | Server: 138 | - cloudflare 139 | referrer-policy: 140 | - strict-origin-when-cross-origin 141 | strict-transport-security: 142 | - max-age=63072000; includeSubDomains; preload 143 | vary: 144 | - Origin 145 | x-content-type-options: 146 | - nosniff 147 | x-frame-options: 148 | - SAMEORIGIN 149 | x-neon-ret-request-id: 150 | - d5bf0743-9ce4-4eeb-9561-5c1dc8bf9d6d 151 | status: 152 | code: 201 153 | message: Created 154 | - request: 155 | body: null 156 | headers: 157 | Accept: 158 | - application/json 159 | Accept-Encoding: 160 | - gzip, deflate 161 | Connection: 162 | - keep-alive 163 | Content-Type: 164 | - application/json 165 | User-Agent: 166 | - neon-client/python version=(0.1.0) 167 | method: GET 168 | uri: https://console.neon.tech/api/v2/projects/solitary-thunder-08372287/endpoints 169 | response: 170 | body: 171 | string: '{"endpoints":[{"host":"ep-dawn-king-a527wlyu.us-east-2.aws.neon.tech","id":"ep-dawn-king-a527wlyu","project_id":"solitary-thunder-08372287","branch_id":"br-calm-union-a59jz26r","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"last_active":"2000-01-01T00:00:00Z","creation_source":"console","created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 172 | headers: 173 | CF-Cache-Status: 174 | - DYNAMIC 175 | CF-RAY: 176 | - 90310b66f83542e6-EWR 177 | Connection: 178 | - keep-alive 179 | Content-Encoding: 180 | - gzip 181 | Content-Type: 182 | - application/json; charset=utf-8 183 | Date: 184 | - Thu, 16 Jan 2025 21:02:05 GMT 185 | Server: 186 | - cloudflare 187 | Transfer-Encoding: 188 | - chunked 189 | referrer-policy: 190 | - strict-origin-when-cross-origin 191 | strict-transport-security: 192 | - max-age=63072000; includeSubDomains; preload 193 | vary: 194 | - Origin 195 | x-content-type-options: 196 | - nosniff 197 | x-frame-options: 198 | - SAMEORIGIN 199 | x-neon-ret-request-id: 200 | - e120990f-1c11-4773-9985-d6e1db9efcea 201 | status: 202 | code: 200 203 | message: OK 204 | - request: 205 | body: null 206 | headers: 207 | Accept: 208 | - application/json 209 | Accept-Encoding: 210 | - gzip, deflate 211 | Connection: 212 | - keep-alive 213 | Content-Type: 214 | - application/json 215 | User-Agent: 216 | - neon-client/python version=(0.1.0) 217 | method: GET 218 | uri: https://console.neon.tech/api/v2/projects/solitary-thunder-08372287/endpoints/ep-dawn-king-a527wlyu 219 | response: 220 | body: 221 | string: '{"endpoint":{"host":"ep-dawn-king-a527wlyu.us-east-2.aws.neon.tech","id":"ep-dawn-king-a527wlyu","project_id":"solitary-thunder-08372287","branch_id":"br-calm-union-a59jz26r","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"last_active":"2000-01-01T00:00:00Z","creation_source":"console","created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}}' 222 | headers: 223 | CF-Cache-Status: 224 | - DYNAMIC 225 | CF-RAY: 226 | - 90310b67cf078c29-EWR 227 | Connection: 228 | - keep-alive 229 | Content-Encoding: 230 | - gzip 231 | Content-Type: 232 | - application/json; charset=utf-8 233 | Date: 234 | - Thu, 16 Jan 2025 21:02:06 GMT 235 | Server: 236 | - cloudflare 237 | Transfer-Encoding: 238 | - chunked 239 | referrer-policy: 240 | - strict-origin-when-cross-origin 241 | strict-transport-security: 242 | - max-age=63072000; includeSubDomains; preload 243 | vary: 244 | - Origin 245 | x-content-type-options: 246 | - nosniff 247 | x-frame-options: 248 | - SAMEORIGIN 249 | x-neon-ret-request-id: 250 | - 6367f193-dace-4315-9826-69bcfbf4f2b1 251 | status: 252 | code: 200 253 | message: OK 254 | version: 1 255 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_get_projects.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[{"id":"winter-frost-26518790","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-480","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0,"autoscaling_limit_max_cu":0,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"primary_branch_only":false},"enable_logical_replication":false},"pg_version":15,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":3072,"branch_logical_size_limit_bytes":3221225472,"store_passwords":true,"active_time":0,"cpu_used_sec":0,"creation_source":"console","created_at":"2024-01-23T22:02:45Z","updated_at":"2024-01-23T22:04:10Z","synthetic_storage_size":0,"quota_reset_at":"2024-02-01T00:00:00Z","owner_id":"838386f6-b5f1-4c3b-89a2-4f5a130ef3de"}],"pagination":{"cursor":"winter-frost-26518790"}}' 20 | headers: 21 | Connection: 22 | - keep-alive 23 | Content-Length: 24 | - '802' 25 | Content-Type: 26 | - application/json 27 | Date: 28 | - Tue, 23 Jan 2024 22:05:48 GMT 29 | Strict-Transport-Security: 30 | - max-age=15724800; includeSubDomains 31 | Vary: 32 | - Origin 33 | X-Neon-Ret-Request-Id: 34 | - a5799bc43c1bab163b924b148fa1fe03 35 | status: 36 | code: 200 37 | message: OK 38 | version: 1 39 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_me.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/users/me 17 | response: 18 | body: 19 | string: '{"active_seconds_limit":0,"billing_account":{"state":"active","payment_source":{"type":""},"subscription_type":"free_v2","payment_method":"none","quota_reset_at_last":"2025-01-01T00:00:00Z","name":"David","email":"david@neon.tech","address_city":"","address_country":"","address_line1":"","address_line2":"","address_postal_code":"","address_state":""},"auth_accounts":[{"email":"david@neon.tech","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c","login":"david","name":"David","provider":"google"},{"email":"david@neon.tech","image":"","login":"david","name":"David","provider":"keycloak"}],"email":"david@neon.tech","id":"92cdd2ee-a6d8-4647-868f-124531123b03","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c","login":"david","name":"David","last_name":"Gomes","projects_limit":10,"branches_limit":10,"max_autoscaling_limit":2,"plan":"free_v2"}' 20 | headers: 21 | CF-Cache-Status: 22 | - DYNAMIC 23 | CF-RAY: 24 | - 90310b381a2f7cac-EWR 25 | Connection: 26 | - keep-alive 27 | Content-Encoding: 28 | - gzip 29 | Content-Type: 30 | - application/json; charset=utf-8 31 | Date: 32 | - Thu, 16 Jan 2025 21:01:58 GMT 33 | Server: 34 | - cloudflare 35 | Transfer-Encoding: 36 | - chunked 37 | referrer-policy: 38 | - strict-origin-when-cross-origin 39 | strict-transport-security: 40 | - max-age=63072000; includeSubDomains; preload 41 | vary: 42 | - Origin 43 | x-content-type-options: 44 | - nosniff 45 | x-frame-options: 46 | - SAMEORIGIN 47 | x-neon-ret-request-id: 48 | - d00d3d9f-319f-4bda-82a2-02701f87119d 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_operations.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[{"id":"dry-hall-87167252","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-5484","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"active_time":0,"cpu_used_sec":0,"creation_source":"console","created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z","synthetic_storage_size":0,"quota_reset_at":"2025-02-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}],"pagination":{"cursor":"dry-hall-87167252"},"applications":{},"integrations":{}}' 20 | headers: 21 | CF-Cache-Status: 22 | - DYNAMIC 23 | CF-RAY: 24 | - 90310b5868677cae-EWR 25 | Connection: 26 | - keep-alive 27 | Content-Encoding: 28 | - gzip 29 | Content-Type: 30 | - application/json; charset=utf-8 31 | Date: 32 | - Thu, 16 Jan 2025 21:02:03 GMT 33 | Server: 34 | - cloudflare 35 | Transfer-Encoding: 36 | - chunked 37 | referrer-policy: 38 | - strict-origin-when-cross-origin 39 | strict-transport-security: 40 | - max-age=63072000; includeSubDomains; preload 41 | vary: 42 | - Origin 43 | x-content-type-options: 44 | - nosniff 45 | x-frame-options: 46 | - SAMEORIGIN 47 | x-neon-ret-request-id: 48 | - 556c54bf-3552-4550-b53a-b4b76195e5e1 49 | status: 50 | code: 200 51 | message: OK 52 | - request: 53 | body: null 54 | headers: 55 | Accept: 56 | - application/json 57 | Accept-Encoding: 58 | - gzip, deflate 59 | Connection: 60 | - keep-alive 61 | Content-Length: 62 | - '0' 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - neon-client/python version=(0.1.0) 67 | method: DELETE 68 | uri: https://console.neon.tech/api/v2/projects/dry-hall-87167252 69 | response: 70 | body: 71 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"dry-hall-87167252","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-5484","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:03Z","updated_at":"2025-01-16T21:02:03Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}}' 72 | headers: 73 | CF-Cache-Status: 74 | - DYNAMIC 75 | CF-RAY: 76 | - 90310b5a19f04261-EWR 77 | Connection: 78 | - keep-alive 79 | Content-Encoding: 80 | - gzip 81 | Content-Type: 82 | - application/json; charset=utf-8 83 | Date: 84 | - Thu, 16 Jan 2025 21:02:03 GMT 85 | Server: 86 | - cloudflare 87 | Transfer-Encoding: 88 | - chunked 89 | referrer-policy: 90 | - strict-origin-when-cross-origin 91 | strict-transport-security: 92 | - max-age=63072000; includeSubDomains; preload 93 | vary: 94 | - Origin 95 | x-content-type-options: 96 | - nosniff 97 | x-frame-options: 98 | - SAMEORIGIN 99 | x-neon-ret-request-id: 100 | - eb1c7a52-5e06-46fd-8d43-0ed914e906b9 101 | status: 102 | code: 200 103 | message: OK 104 | - request: 105 | body: '{"project": {"name": "pytest-9154"}}' 106 | headers: 107 | Accept: 108 | - application/json 109 | Accept-Encoding: 110 | - gzip, deflate 111 | Connection: 112 | - keep-alive 113 | Content-Length: 114 | - '36' 115 | Content-Type: 116 | - application/json 117 | User-Agent: 118 | - neon-client/python version=(0.1.0) 119 | method: POST 120 | uri: https://console.neon.tech/api/v2/projects 121 | response: 122 | body: 123 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"solitary-cell-58208823","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-9154","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"},"connection_uris":[{"connection_uri":"postgresql://neondb_owner:hj6MSzbYlNw8@ep-lingering-surf-a5926aiu.us-east-2.aws.neon.tech/neondb?sslmode=require","connection_parameters":{"database":"neondb","password":"hj6MSzbYlNw8","role":"neondb_owner","host":"ep-lingering-surf-a5926aiu.us-east-2.aws.neon.tech","pooler_host":"ep-lingering-surf-a5926aiu-pooler.us-east-2.aws.neon.tech"}}],"roles":[{"branch_id":"br-yellow-dawn-a5qftt8g","name":"neondb_owner","password":"hj6MSzbYlNw8","protected":false,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z"}],"databases":[{"id":39724310,"branch_id":"br-yellow-dawn-a5qftt8g","name":"neondb","owner_name":"neondb_owner","created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z"}],"operations":[{"id":"69de6dd4-597f-404f-a095-c96702407d7b","project_id":"solitary-cell-58208823","branch_id":"br-yellow-dawn-a5qftt8g","action":"create_timeline","status":"running","failures_count":0,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","total_duration_ms":0},{"id":"d6ad21e4-725d-493f-a495-43867ad88d09","project_id":"solitary-cell-58208823","branch_id":"br-yellow-dawn-a5qftt8g","endpoint_id":"ep-lingering-surf-a5926aiu","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","total_duration_ms":0}],"branch":{"id":"br-yellow-dawn-a5qftt8g","project_id":"solitary-cell-58208823","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:04Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z"},"endpoints":[{"host":"ep-lingering-surf-a5926aiu.us-east-2.aws.neon.tech","id":"ep-lingering-surf-a5926aiu","project_id":"solitary-cell-58208823","branch_id":"br-yellow-dawn-a5qftt8g","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"creation_source":"console","created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 124 | headers: 125 | CF-Cache-Status: 126 | - DYNAMIC 127 | CF-RAY: 128 | - 90310b5b4c2f4356-EWR 129 | Connection: 130 | - keep-alive 131 | Content-Length: 132 | - '3474' 133 | Content-Type: 134 | - application/json; charset=utf-8 135 | Date: 136 | - Thu, 16 Jan 2025 21:02:04 GMT 137 | Server: 138 | - cloudflare 139 | referrer-policy: 140 | - strict-origin-when-cross-origin 141 | strict-transport-security: 142 | - max-age=63072000; includeSubDomains; preload 143 | vary: 144 | - Origin 145 | x-content-type-options: 146 | - nosniff 147 | x-frame-options: 148 | - SAMEORIGIN 149 | x-neon-ret-request-id: 150 | - 6eda115f-34a4-4591-b15d-19a7fa46bfbb 151 | status: 152 | code: 201 153 | message: Created 154 | - request: 155 | body: null 156 | headers: 157 | Accept: 158 | - application/json 159 | Accept-Encoding: 160 | - gzip, deflate 161 | Connection: 162 | - keep-alive 163 | Content-Type: 164 | - application/json 165 | User-Agent: 166 | - neon-client/python version=(0.1.0) 167 | method: GET 168 | uri: https://console.neon.tech/api/v2/projects/solitary-cell-58208823/branches 169 | response: 170 | body: 171 | string: '{"branches":[{"id":"br-yellow-dawn-a5qftt8g","project_id":"solitary-cell-58208823","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:04Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","created_by":{"name":"David","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"}}],"annotations":{},"pagination":{"next":"eyJicmFuY2hfaWQiOiJici15ZWxsb3ctZGF3bi1hNXFmdHQ4ZyIsInNvcnRfYnkiOiJ1cGRhdGVkX2F0Iiwic29ydF9ieV92YWx1ZSI6IjIwMjUtMDEtMTZUMjE6MDI6MDRaIiwic29ydF9vcmRlciI6IkRFU0MifQ==","sort_by":"updated_at","sort_order":"DESC"}}' 172 | headers: 173 | CF-Cache-Status: 174 | - DYNAMIC 175 | CF-RAY: 176 | - 90310b5d4a880c7e-EWR 177 | Connection: 178 | - keep-alive 179 | Content-Encoding: 180 | - gzip 181 | Content-Type: 182 | - application/json; charset=utf-8 183 | Date: 184 | - Thu, 16 Jan 2025 21:02:04 GMT 185 | Server: 186 | - cloudflare 187 | Transfer-Encoding: 188 | - chunked 189 | referrer-policy: 190 | - strict-origin-when-cross-origin 191 | strict-transport-security: 192 | - max-age=63072000; includeSubDomains; preload 193 | vary: 194 | - Origin 195 | x-content-type-options: 196 | - nosniff 197 | x-frame-options: 198 | - SAMEORIGIN 199 | x-neon-ret-request-id: 200 | - 3a4b8a99-6ae7-4ab7-9c03-2a9a18805b2c 201 | status: 202 | code: 200 203 | message: OK 204 | - request: 205 | body: null 206 | headers: 207 | Accept: 208 | - application/json 209 | Accept-Encoding: 210 | - gzip, deflate 211 | Connection: 212 | - keep-alive 213 | Content-Type: 214 | - application/json 215 | User-Agent: 216 | - neon-client/python version=(0.1.0) 217 | method: GET 218 | uri: https://console.neon.tech/api/v2/projects/solitary-cell-58208823/operations 219 | response: 220 | body: 221 | string: '{"operations":[{"id":"d6ad21e4-725d-493f-a495-43867ad88d09","project_id":"solitary-cell-58208823","branch_id":"br-yellow-dawn-a5qftt8g","endpoint_id":"ep-lingering-surf-a5926aiu","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","total_duration_ms":0},{"id":"69de6dd4-597f-404f-a095-c96702407d7b","project_id":"solitary-cell-58208823","branch_id":"br-yellow-dawn-a5qftt8g","action":"create_timeline","status":"running","failures_count":0,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","total_duration_ms":31}],"pagination":{"cursor":"2025-01-16T21:02:04.214837Z"}}' 222 | headers: 223 | CF-Cache-Status: 224 | - DYNAMIC 225 | CF-RAY: 226 | - 90310b5f3e4fc351-EWR 227 | Connection: 228 | - keep-alive 229 | Content-Encoding: 230 | - gzip 231 | Content-Type: 232 | - application/json; charset=utf-8 233 | Date: 234 | - Thu, 16 Jan 2025 21:02:04 GMT 235 | Server: 236 | - cloudflare 237 | Transfer-Encoding: 238 | - chunked 239 | referrer-policy: 240 | - strict-origin-when-cross-origin 241 | strict-transport-security: 242 | - max-age=63072000; includeSubDomains; preload 243 | vary: 244 | - Origin 245 | x-content-type-options: 246 | - nosniff 247 | x-frame-options: 248 | - SAMEORIGIN 249 | x-neon-ret-request-id: 250 | - ba1612f6-a8a9-44b4-b0b8-82aabbf4ba91 251 | status: 252 | code: 200 253 | message: OK 254 | - request: 255 | body: null 256 | headers: 257 | Accept: 258 | - application/json 259 | Accept-Encoding: 260 | - gzip, deflate 261 | Connection: 262 | - keep-alive 263 | Content-Type: 264 | - application/json 265 | User-Agent: 266 | - neon-client/python version=(0.1.0) 267 | method: GET 268 | uri: https://console.neon.tech/api/v2/projects/solitary-cell-58208823/operations/d6ad21e4-725d-493f-a495-43867ad88d09 269 | response: 270 | body: 271 | string: '{"operation":{"id":"d6ad21e4-725d-493f-a495-43867ad88d09","project_id":"solitary-cell-58208823","branch_id":"br-yellow-dawn-a5qftt8g","endpoint_id":"ep-lingering-surf-a5926aiu","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:04Z","updated_at":"2025-01-16T21:02:04Z","total_duration_ms":0}}' 272 | headers: 273 | CF-Cache-Status: 274 | - DYNAMIC 275 | CF-RAY: 276 | - 90310b606ead4201-EWR 277 | Connection: 278 | - keep-alive 279 | Content-Encoding: 280 | - gzip 281 | Content-Type: 282 | - application/json; charset=utf-8 283 | Date: 284 | - Thu, 16 Jan 2025 21:02:04 GMT 285 | Server: 286 | - cloudflare 287 | Transfer-Encoding: 288 | - chunked 289 | referrer-policy: 290 | - strict-origin-when-cross-origin 291 | strict-transport-security: 292 | - max-age=63072000; includeSubDomains; preload 293 | vary: 294 | - Origin 295 | x-content-type-options: 296 | - nosniff 297 | x-frame-options: 298 | - SAMEORIGIN 299 | x-neon-ret-request-id: 300 | - b9c8e2f7-c3be-4a12-bdd3-7498dc4b78ae 301 | status: 302 | code: 200 303 | message: OK 304 | version: 1 305 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_project.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[{"id":"aged-salad-46255801","platform_id":"azure","region_id":"azure-eastus2","name":"test","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":2,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":16,"proxy_host":"eastus2.azure.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"active_time":1340,"cpu_used_sec":343,"creation_source":"console","created_at":"2024-12-27T17:00:44Z","updated_at":"2025-01-13T04:08:18Z","synthetic_storage_size":46881408,"quota_reset_at":"2025-02-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03","compute_last_active_at":"2025-01-13T04:08:09Z"}],"pagination":{"cursor":"aged-salad-46255801"},"applications":{},"integrations":{}}' 20 | headers: 21 | CF-Cache-Status: 22 | - DYNAMIC 23 | CF-RAY: 24 | - 90310b3cdc8b5e82-EWR 25 | Connection: 26 | - keep-alive 27 | Content-Encoding: 28 | - gzip 29 | Content-Type: 30 | - application/json; charset=utf-8 31 | Date: 32 | - Thu, 16 Jan 2025 21:01:59 GMT 33 | Server: 34 | - cloudflare 35 | Transfer-Encoding: 36 | - chunked 37 | referrer-policy: 38 | - strict-origin-when-cross-origin 39 | strict-transport-security: 40 | - max-age=63072000; includeSubDomains; preload 41 | vary: 42 | - Origin 43 | x-content-type-options: 44 | - nosniff 45 | x-frame-options: 46 | - SAMEORIGIN 47 | x-neon-ret-request-id: 48 | - 000a25e6-8677-4268-a0ae-89d8fc72e7ee 49 | status: 50 | code: 200 51 | message: OK 52 | - request: 53 | body: null 54 | headers: 55 | Accept: 56 | - application/json 57 | Accept-Encoding: 58 | - gzip, deflate 59 | Connection: 60 | - keep-alive 61 | Content-Length: 62 | - '0' 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - neon-client/python version=(0.1.0) 67 | method: DELETE 68 | uri: https://console.neon.tech/api/v2/projects/aged-salad-46255801 69 | response: 70 | body: 71 | string: '{"project":{"data_storage_bytes_hour":15807655176,"data_transfer_bytes":27735,"written_data_bytes":21858512,"compute_time_seconds":343,"active_time_seconds":1340,"cpu_used_sec":343,"id":"aged-salad-46255801","platform_id":"azure","region_id":"azure-eastus2","name":"test","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":2,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":16,"proxy_host":"eastus2.azure.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2024-12-27T17:00:44Z","updated_at":"2025-01-13T04:08:18Z","synthetic_storage_size":46881408,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03","compute_last_active_at":"2025-01-13T04:08:09Z"}}' 72 | headers: 73 | CF-Cache-Status: 74 | - DYNAMIC 75 | CF-RAY: 76 | - 90310b3e581e42bf-EWR 77 | Connection: 78 | - keep-alive 79 | Content-Encoding: 80 | - gzip 81 | Content-Type: 82 | - application/json; charset=utf-8 83 | Date: 84 | - Thu, 16 Jan 2025 21:01:59 GMT 85 | Server: 86 | - cloudflare 87 | Transfer-Encoding: 88 | - chunked 89 | referrer-policy: 90 | - strict-origin-when-cross-origin 91 | strict-transport-security: 92 | - max-age=63072000; includeSubDomains; preload 93 | vary: 94 | - Origin 95 | x-content-type-options: 96 | - nosniff 97 | x-frame-options: 98 | - SAMEORIGIN 99 | x-neon-ret-request-id: 100 | - 4fc0bdff-5ac4-4a41-b8af-d98ac8f2f7d1 101 | status: 102 | code: 200 103 | message: OK 104 | - request: 105 | body: '{"project": {"name": "pytest-6473"}}' 106 | headers: 107 | Accept: 108 | - application/json 109 | Accept-Encoding: 110 | - gzip, deflate 111 | Connection: 112 | - keep-alive 113 | Content-Length: 114 | - '36' 115 | Content-Type: 116 | - application/json 117 | User-Agent: 118 | - neon-client/python version=(0.1.0) 119 | method: POST 120 | uri: https://console.neon.tech/api/v2/projects 121 | response: 122 | body: 123 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"long-forest-06846227","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-6473","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:01:59Z","consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"},"connection_uris":[{"connection_uri":"postgresql://neondb_owner:GehbNIcf6tQ7@ep-jolly-frost-a57qv4wh.us-east-2.aws.neon.tech/neondb?sslmode=require","connection_parameters":{"database":"neondb","password":"GehbNIcf6tQ7","role":"neondb_owner","host":"ep-jolly-frost-a57qv4wh.us-east-2.aws.neon.tech","pooler_host":"ep-jolly-frost-a57qv4wh-pooler.us-east-2.aws.neon.tech"}}],"roles":[{"branch_id":"br-floral-wind-a5090hvr","name":"neondb_owner","password":"GehbNIcf6tQ7","protected":false,"created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:01:59Z"}],"databases":[{"id":39724307,"branch_id":"br-floral-wind-a5090hvr","name":"neondb","owner_name":"neondb_owner","created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:01:59Z"}],"operations":[{"id":"389d2336-3e9f-41b6-9042-5f06317b30d4","project_id":"long-forest-06846227","branch_id":"br-floral-wind-a5090hvr","action":"create_timeline","status":"running","failures_count":0,"created_at":"2025-01-16T21:02:00Z","updated_at":"2025-01-16T21:02:00Z","total_duration_ms":0},{"id":"4ddd4212-07ac-4264-aa43-d39702b504e5","project_id":"long-forest-06846227","branch_id":"br-floral-wind-a5090hvr","endpoint_id":"ep-jolly-frost-a57qv4wh","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:00Z","updated_at":"2025-01-16T21:02:00Z","total_duration_ms":0}],"branch":{"id":"br-floral-wind-a5090hvr","project_id":"long-forest-06846227","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:01:59Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:01:59Z"},"endpoints":[{"host":"ep-jolly-frost-a57qv4wh.us-east-2.aws.neon.tech","id":"ep-jolly-frost-a57qv4wh","project_id":"long-forest-06846227","branch_id":"br-floral-wind-a5090hvr","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"creation_source":"console","created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:01:59Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 124 | headers: 125 | CF-Cache-Status: 126 | - DYNAMIC 127 | CF-RAY: 128 | - 90310b40bb6f7cf0-EWR 129 | Connection: 130 | - keep-alive 131 | Content-Length: 132 | - '3446' 133 | Content-Type: 134 | - application/json; charset=utf-8 135 | Date: 136 | - Thu, 16 Jan 2025 21:02:00 GMT 137 | Server: 138 | - cloudflare 139 | referrer-policy: 140 | - strict-origin-when-cross-origin 141 | strict-transport-security: 142 | - max-age=63072000; includeSubDomains; preload 143 | vary: 144 | - Origin 145 | x-content-type-options: 146 | - nosniff 147 | x-frame-options: 148 | - SAMEORIGIN 149 | x-neon-ret-request-id: 150 | - 0ff3a492-17ce-4304-9022-e4299c37acb2 151 | status: 152 | code: 201 153 | message: Created 154 | - request: 155 | body: null 156 | headers: 157 | Accept: 158 | - application/json 159 | Accept-Encoding: 160 | - gzip, deflate 161 | Connection: 162 | - keep-alive 163 | Content-Type: 164 | - application/json 165 | User-Agent: 166 | - neon-client/python version=(0.1.0) 167 | method: GET 168 | uri: https://console.neon.tech/api/v2/projects/long-forest-06846227 169 | response: 170 | body: 171 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"long-forest-06846227","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-6473","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:01:59Z","synthetic_storage_size":0,"consumption_period_start":"2025-01-01T00:00:00Z","consumption_period_end":"2025-02-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03","owner":{"email":"david@neon.tech","name":"David 172 | Gomes","branches_limit":10,"subscription_type":"free_v2"}}}' 173 | headers: 174 | CF-Cache-Status: 175 | - DYNAMIC 176 | CF-RAY: 177 | - 90310b43fb6c7c88-EWR 178 | Connection: 179 | - keep-alive 180 | Content-Encoding: 181 | - gzip 182 | Content-Type: 183 | - application/json; charset=utf-8 184 | Date: 185 | - Thu, 16 Jan 2025 21:02:00 GMT 186 | Server: 187 | - cloudflare 188 | Transfer-Encoding: 189 | - chunked 190 | referrer-policy: 191 | - strict-origin-when-cross-origin 192 | strict-transport-security: 193 | - max-age=63072000; includeSubDomains; preload 194 | vary: 195 | - Origin 196 | x-content-type-options: 197 | - nosniff 198 | x-frame-options: 199 | - SAMEORIGIN 200 | x-neon-ret-request-id: 201 | - 90e48bfc-5d79-41a1-bca0-1fe865c625c8 202 | status: 203 | code: 200 204 | message: OK 205 | - request: 206 | body: '{"project": {"name": "pytest-3773"}}' 207 | headers: 208 | Accept: 209 | - application/json 210 | Accept-Encoding: 211 | - gzip, deflate 212 | Connection: 213 | - keep-alive 214 | Content-Length: 215 | - '36' 216 | Content-Type: 217 | - application/json 218 | User-Agent: 219 | - neon-client/python version=(0.1.0) 220 | method: PATCH 221 | uri: https://console.neon.tech/api/v2/projects/long-forest-06846227 222 | response: 223 | body: 224 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"long-forest-06846227","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-3773","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:02:00Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"},"operations":[]}' 225 | headers: 226 | CF-Cache-Status: 227 | - DYNAMIC 228 | CF-RAY: 229 | - 90310b451fe332ee-EWR 230 | Connection: 231 | - keep-alive 232 | Content-Encoding: 233 | - gzip 234 | Content-Type: 235 | - application/json; charset=utf-8 236 | Date: 237 | - Thu, 16 Jan 2025 21:02:00 GMT 238 | Server: 239 | - cloudflare 240 | Transfer-Encoding: 241 | - chunked 242 | referrer-policy: 243 | - strict-origin-when-cross-origin 244 | strict-transport-security: 245 | - max-age=63072000; includeSubDomains; preload 246 | vary: 247 | - Origin 248 | x-content-type-options: 249 | - nosniff 250 | x-frame-options: 251 | - SAMEORIGIN 252 | x-neon-ret-request-id: 253 | - 39ba66dd-55ae-423c-8574-3137fac75384 254 | status: 255 | code: 200 256 | message: OK 257 | - request: 258 | body: null 259 | headers: 260 | Accept: 261 | - application/json 262 | Accept-Encoding: 263 | - gzip, deflate 264 | Connection: 265 | - keep-alive 266 | Content-Type: 267 | - application/json 268 | User-Agent: 269 | - neon-client/python version=(0.1.0) 270 | method: GET 271 | uri: https://console.neon.tech/api/v2/projects 272 | response: 273 | body: 274 | string: '{"projects":[{"id":"long-forest-06846227","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-3773","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"active_time":0,"cpu_used_sec":0,"creation_source":"console","created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:02:00Z","synthetic_storage_size":0,"quota_reset_at":"2025-02-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}],"pagination":{"cursor":"long-forest-06846227"},"applications":{},"integrations":{}}' 275 | headers: 276 | CF-Cache-Status: 277 | - DYNAMIC 278 | CF-RAY: 279 | - 90310b468eb85e64-EWR 280 | Connection: 281 | - keep-alive 282 | Content-Encoding: 283 | - gzip 284 | Content-Type: 285 | - application/json; charset=utf-8 286 | Date: 287 | - Thu, 16 Jan 2025 21:02:00 GMT 288 | Server: 289 | - cloudflare 290 | Transfer-Encoding: 291 | - chunked 292 | referrer-policy: 293 | - strict-origin-when-cross-origin 294 | strict-transport-security: 295 | - max-age=63072000; includeSubDomains; preload 296 | vary: 297 | - Origin 298 | x-content-type-options: 299 | - nosniff 300 | x-frame-options: 301 | - SAMEORIGIN 302 | x-neon-ret-request-id: 303 | - c0c94635-52f3-44ea-829f-748f121e6b47 304 | status: 305 | code: 200 306 | message: OK 307 | - request: 308 | body: null 309 | headers: 310 | Accept: 311 | - application/json 312 | Accept-Encoding: 313 | - gzip, deflate 314 | Connection: 315 | - keep-alive 316 | Content-Type: 317 | - application/json 318 | User-Agent: 319 | - neon-client/python version=(0.1.0) 320 | method: GET 321 | uri: https://console.neon.tech/api/v2/projects/shared 322 | response: 323 | body: 324 | string: '{"projects":[]}' 325 | headers: 326 | CF-Cache-Status: 327 | - DYNAMIC 328 | CF-RAY: 329 | - 90310b47edab43cb-EWR 330 | Connection: 331 | - keep-alive 332 | Content-Length: 333 | - '15' 334 | Content-Type: 335 | - application/json; charset=utf-8 336 | Date: 337 | - Thu, 16 Jan 2025 21:02:01 GMT 338 | Server: 339 | - cloudflare 340 | referrer-policy: 341 | - strict-origin-when-cross-origin 342 | strict-transport-security: 343 | - max-age=63072000; includeSubDomains; preload 344 | vary: 345 | - Origin 346 | x-content-type-options: 347 | - nosniff 348 | x-frame-options: 349 | - SAMEORIGIN 350 | x-neon-ret-request-id: 351 | - 277ee33d-9f6c-4a3a-a46d-ad45d599b3e5 352 | status: 353 | code: 200 354 | message: OK 355 | - request: 356 | body: null 357 | headers: 358 | Accept: 359 | - application/json 360 | Accept-Encoding: 361 | - gzip, deflate 362 | Connection: 363 | - keep-alive 364 | Content-Type: 365 | - application/json 366 | User-Agent: 367 | - neon-client/python version=(0.1.0) 368 | method: GET 369 | uri: https://console.neon.tech/api/v2/projects/long-forest-06846227/connection_uri?database_name=neondb&role_name=neondb_owner&pooled=True 370 | response: 371 | body: 372 | string: '{"uri":"postgresql://neondb_owner:GehbNIcf6tQ7@ep-jolly-frost-a57qv4wh-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require"}' 373 | headers: 374 | CF-Cache-Status: 375 | - DYNAMIC 376 | CF-RAY: 377 | - 90310b48f88842ce-EWR 378 | Connection: 379 | - keep-alive 380 | Content-Encoding: 381 | - gzip 382 | Content-Type: 383 | - application/json; charset=utf-8 384 | Date: 385 | - Thu, 16 Jan 2025 21:02:01 GMT 386 | Server: 387 | - cloudflare 388 | Transfer-Encoding: 389 | - chunked 390 | referrer-policy: 391 | - strict-origin-when-cross-origin 392 | strict-transport-security: 393 | - max-age=63072000; includeSubDomains; preload 394 | vary: 395 | - Origin 396 | x-content-type-options: 397 | - nosniff 398 | x-frame-options: 399 | - SAMEORIGIN 400 | x-neon-ret-request-id: 401 | - 8944b8b4-5cd2-4aa0-9c76-b282be1262fa 402 | status: 403 | code: 200 404 | message: OK 405 | - request: 406 | body: null 407 | headers: 408 | Accept: 409 | - application/json 410 | Accept-Encoding: 411 | - gzip, deflate 412 | Connection: 413 | - keep-alive 414 | Content-Length: 415 | - '0' 416 | Content-Type: 417 | - application/json 418 | User-Agent: 419 | - neon-client/python version=(0.1.0) 420 | method: DELETE 421 | uri: https://console.neon.tech/api/v2/projects/long-forest-06846227 422 | response: 423 | body: 424 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"long-forest-06846227","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-3773","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:01:59Z","updated_at":"2025-01-16T21:02:00Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}}' 425 | headers: 426 | CF-Cache-Status: 427 | - DYNAMIC 428 | CF-RAY: 429 | - 90310b4a4d8442a6-EWR 430 | Connection: 431 | - keep-alive 432 | Content-Encoding: 433 | - gzip 434 | Content-Type: 435 | - application/json; charset=utf-8 436 | Date: 437 | - Thu, 16 Jan 2025 21:02:01 GMT 438 | Server: 439 | - cloudflare 440 | Transfer-Encoding: 441 | - chunked 442 | referrer-policy: 443 | - strict-origin-when-cross-origin 444 | strict-transport-security: 445 | - max-age=63072000; includeSubDomains; preload 446 | vary: 447 | - Origin 448 | x-content-type-options: 449 | - nosniff 450 | x-frame-options: 451 | - SAMEORIGIN 452 | x-neon-ret-request-id: 453 | - 4c0ee11b-a6a4-4ab0-bd54-92ed122d85f8 454 | status: 455 | code: 200 456 | message: OK 457 | version: 1 458 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_project_delete.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[]}' 20 | headers: 21 | Connection: 22 | - keep-alive 23 | Content-Length: 24 | - '15' 25 | Content-Type: 26 | - application/json 27 | Date: 28 | - Tue, 23 Jan 2024 21:49:06 GMT 29 | Strict-Transport-Security: 30 | - max-age=15724800; includeSubDomains 31 | Vary: 32 | - Origin 33 | X-Neon-Ret-Request-Id: 34 | - a718c5bcf841f63063cd65a793c68d0b 35 | status: 36 | code: 200 37 | message: OK 38 | - request: 39 | body: '{}' 40 | headers: 41 | Accept: 42 | - application/json 43 | Accept-Encoding: 44 | - gzip, deflate 45 | Connection: 46 | - keep-alive 47 | Content-Length: 48 | - '2' 49 | Content-Type: 50 | - application/json 51 | User-Agent: 52 | - neon-client/python version=(0.1.0) 53 | method: POST 54 | uri: https://console.neon.tech/api/v2/projects 55 | response: 56 | body: 57 | string: '{"code":"","message":"invalid request: decode application/json: invalid: 58 | project (field required)"}' 59 | headers: 60 | Connection: 61 | - keep-alive 62 | Content-Length: 63 | - '99' 64 | Content-Type: 65 | - application/json 66 | Date: 67 | - Tue, 23 Jan 2024 21:49:06 GMT 68 | Strict-Transport-Security: 69 | - max-age=15724800; includeSubDomains 70 | Vary: 71 | - Origin 72 | X-Neon-Ret-Request-Id: 73 | - 5e32ba21b25aad1a95df172805aca86a 74 | status: 75 | code: 400 76 | message: Bad Request 77 | version: 1 78 | -------------------------------------------------------------------------------- /tests/cassettes/test_integration/test_roles.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Type: 12 | - application/json 13 | User-Agent: 14 | - neon-client/python version=(0.1.0) 15 | method: GET 16 | uri: https://console.neon.tech/api/v2/projects 17 | response: 18 | body: 19 | string: '{"projects":[{"id":"solitary-thunder-08372287","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-2420","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"active_time":0,"cpu_used_sec":0,"creation_source":"console","created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","synthetic_storage_size":0,"quota_reset_at":"2025-02-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}],"pagination":{"cursor":"solitary-thunder-08372287"},"applications":{},"integrations":{}}' 20 | headers: 21 | CF-Cache-Status: 22 | - DYNAMIC 23 | CF-RAY: 24 | - 90310b6998358c7d-EWR 25 | Connection: 26 | - keep-alive 27 | Content-Encoding: 28 | - gzip 29 | Content-Type: 30 | - application/json; charset=utf-8 31 | Date: 32 | - Thu, 16 Jan 2025 21:02:06 GMT 33 | Server: 34 | - cloudflare 35 | Transfer-Encoding: 36 | - chunked 37 | referrer-policy: 38 | - strict-origin-when-cross-origin 39 | strict-transport-security: 40 | - max-age=63072000; includeSubDomains; preload 41 | vary: 42 | - Origin 43 | x-content-type-options: 44 | - nosniff 45 | x-frame-options: 46 | - SAMEORIGIN 47 | x-neon-ret-request-id: 48 | - 8f3ce189-4dea-4af1-86b4-86b7269c861d 49 | status: 50 | code: 200 51 | message: OK 52 | - request: 53 | body: null 54 | headers: 55 | Accept: 56 | - application/json 57 | Accept-Encoding: 58 | - gzip, deflate 59 | Connection: 60 | - keep-alive 61 | Content-Length: 62 | - '0' 63 | Content-Type: 64 | - application/json 65 | User-Agent: 66 | - neon-client/python version=(0.1.0) 67 | method: DELETE 68 | uri: https://console.neon.tech/api/v2/projects/solitary-thunder-08372287 69 | response: 70 | body: 71 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"solitary-thunder-08372287","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-2420","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:05Z","updated_at":"2025-01-16T21:02:05Z","synthetic_storage_size":0,"consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"}}' 72 | headers: 73 | CF-Cache-Status: 74 | - DYNAMIC 75 | CF-RAY: 76 | - 90310b6b6eee7286-EWR 77 | Connection: 78 | - keep-alive 79 | Content-Encoding: 80 | - gzip 81 | Content-Type: 82 | - application/json; charset=utf-8 83 | Date: 84 | - Thu, 16 Jan 2025 21:02:07 GMT 85 | Server: 86 | - cloudflare 87 | Transfer-Encoding: 88 | - chunked 89 | referrer-policy: 90 | - strict-origin-when-cross-origin 91 | strict-transport-security: 92 | - max-age=63072000; includeSubDomains; preload 93 | vary: 94 | - Origin 95 | x-content-type-options: 96 | - nosniff 97 | x-frame-options: 98 | - SAMEORIGIN 99 | x-neon-ret-request-id: 100 | - 7e93af16-c15f-4762-acaa-f178de40b691 101 | status: 102 | code: 200 103 | message: OK 104 | - request: 105 | body: '{"project": {"name": "pytest-1640"}}' 106 | headers: 107 | Accept: 108 | - application/json 109 | Accept-Encoding: 110 | - gzip, deflate 111 | Connection: 112 | - keep-alive 113 | Content-Length: 114 | - '36' 115 | Content-Type: 116 | - application/json 117 | User-Agent: 118 | - neon-client/python version=(0.1.0) 119 | method: POST 120 | uri: https://console.neon.tech/api/v2/projects 121 | response: 122 | body: 123 | string: '{"project":{"data_storage_bytes_hour":0,"data_transfer_bytes":0,"written_data_bytes":0,"compute_time_seconds":0,"active_time_seconds":0,"cpu_used_sec":0,"id":"winter-smoke-11077351","platform_id":"aws","region_id":"aws-us-east-2","name":"pytest-1640","provisioner":"k8s-neonvm","default_endpoint_settings":{"autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"suspend_timeout_seconds":0},"settings":{"allowed_ips":{"ips":[],"protected_branches_only":false},"enable_logical_replication":false,"block_public_connections":false,"block_vpc_connections":false},"pg_version":17,"proxy_host":"us-east-2.aws.neon.tech","branch_logical_size_limit":512,"branch_logical_size_limit_bytes":536870912,"store_passwords":true,"creation_source":"console","history_retention_seconds":86400,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z","consumption_period_start":"0001-01-01T00:00:00Z","consumption_period_end":"0001-01-01T00:00:00Z","owner_id":"92cdd2ee-a6d8-4647-868f-124531123b03"},"connection_uris":[{"connection_uri":"postgresql://neondb_owner:9UPN7YhIiSJQ@ep-royal-brook-a5kfgaqo.us-east-2.aws.neon.tech/neondb?sslmode=require","connection_parameters":{"database":"neondb","password":"9UPN7YhIiSJQ","role":"neondb_owner","host":"ep-royal-brook-a5kfgaqo.us-east-2.aws.neon.tech","pooler_host":"ep-royal-brook-a5kfgaqo-pooler.us-east-2.aws.neon.tech"}}],"roles":[{"branch_id":"br-late-rice-a5cf06kb","name":"neondb_owner","password":"9UPN7YhIiSJQ","protected":false,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z"}],"databases":[{"id":39724312,"branch_id":"br-late-rice-a5cf06kb","name":"neondb","owner_name":"neondb_owner","created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z"}],"operations":[{"id":"a5a5b49f-26ff-424f-805b-9a8488c5e2ba","project_id":"winter-smoke-11077351","branch_id":"br-late-rice-a5cf06kb","action":"create_timeline","status":"running","failures_count":0,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z","total_duration_ms":0},{"id":"e66abb12-3cdd-4b55-87f2-8a2d02ce5199","project_id":"winter-smoke-11077351","branch_id":"br-late-rice-a5cf06kb","endpoint_id":"ep-royal-brook-a5kfgaqo","action":"start_compute","status":"scheduling","failures_count":0,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z","total_duration_ms":0}],"branch":{"id":"br-late-rice-a5cf06kb","project_id":"winter-smoke-11077351","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:07Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z"},"endpoints":[{"host":"ep-royal-brook-a5kfgaqo.us-east-2.aws.neon.tech","id":"ep-royal-brook-a5kfgaqo","project_id":"winter-smoke-11077351","branch_id":"br-late-rice-a5cf06kb","autoscaling_limit_min_cu":0.25,"autoscaling_limit_max_cu":0.25,"region_id":"aws-us-east-2","type":"read_write","current_state":"init","pending_state":"active","settings":{},"pooler_enabled":false,"pooler_mode":"transaction","disabled":false,"passwordless_access":true,"creation_source":"console","created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z","proxy_host":"us-east-2.aws.neon.tech","suspend_timeout_seconds":0,"provisioner":"k8s-neonvm"}]}' 124 | headers: 125 | CF-Cache-Status: 126 | - DYNAMIC 127 | CF-RAY: 128 | - 90310b6ebe0d1a1b-EWR 129 | Connection: 130 | - keep-alive 131 | Content-Length: 132 | - '3439' 133 | Content-Type: 134 | - application/json; charset=utf-8 135 | Date: 136 | - Thu, 16 Jan 2025 21:02:07 GMT 137 | Server: 138 | - cloudflare 139 | referrer-policy: 140 | - strict-origin-when-cross-origin 141 | strict-transport-security: 142 | - max-age=63072000; includeSubDomains; preload 143 | vary: 144 | - Origin 145 | x-content-type-options: 146 | - nosniff 147 | x-frame-options: 148 | - SAMEORIGIN 149 | x-neon-ret-request-id: 150 | - b5edd29e-d856-4e1b-a42d-e240ac4efc65 151 | status: 152 | code: 201 153 | message: Created 154 | - request: 155 | body: null 156 | headers: 157 | Accept: 158 | - application/json 159 | Accept-Encoding: 160 | - gzip, deflate 161 | Connection: 162 | - keep-alive 163 | Content-Type: 164 | - application/json 165 | User-Agent: 166 | - neon-client/python version=(0.1.0) 167 | method: GET 168 | uri: https://console.neon.tech/api/v2/projects/winter-smoke-11077351/branches 169 | response: 170 | body: 171 | string: '{"branches":[{"id":"br-late-rice-a5cf06kb","project_id":"winter-smoke-11077351","name":"main","current_state":"init","pending_state":"ready","state_changed_at":"2025-01-16T21:02:07Z","creation_source":"console","primary":true,"default":true,"protected":false,"cpu_used_sec":0,"compute_time_seconds":0,"active_time_seconds":0,"written_data_bytes":0,"data_transfer_bytes":0,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z","created_by":{"name":"David","image":"https://lh3.googleusercontent.com/a/ACg8ocIW4y_ki038IdI89ZxbbfuRuXtJyTuDYdTJZkMzWLaBHEdN7AY=s96-c"}}],"annotations":{},"pagination":{"next":"eyJicmFuY2hfaWQiOiJici1sYXRlLXJpY2UtYTVjZjA2a2IiLCJzb3J0X2J5IjoidXBkYXRlZF9hdCIsInNvcnRfYnlfdmFsdWUiOiIyMDI1LTAxLTE2VDIxOjAyOjA3WiIsInNvcnRfb3JkZXIiOiJERVNDIn0=","sort_by":"updated_at","sort_order":"DESC"}}' 172 | headers: 173 | CF-Cache-Status: 174 | - DYNAMIC 175 | CF-RAY: 176 | - 90310b7188eb8c9c-EWR 177 | Connection: 178 | - keep-alive 179 | Content-Encoding: 180 | - gzip 181 | Content-Type: 182 | - application/json; charset=utf-8 183 | Date: 184 | - Thu, 16 Jan 2025 21:02:07 GMT 185 | Server: 186 | - cloudflare 187 | Transfer-Encoding: 188 | - chunked 189 | referrer-policy: 190 | - strict-origin-when-cross-origin 191 | strict-transport-security: 192 | - max-age=63072000; includeSubDomains; preload 193 | vary: 194 | - Origin 195 | x-content-type-options: 196 | - nosniff 197 | x-frame-options: 198 | - SAMEORIGIN 199 | x-neon-ret-request-id: 200 | - b0000e72-baf9-42d0-abb1-21bd9c0a1af1 201 | status: 202 | code: 200 203 | message: OK 204 | - request: 205 | body: null 206 | headers: 207 | Accept: 208 | - application/json 209 | Accept-Encoding: 210 | - gzip, deflate 211 | Connection: 212 | - keep-alive 213 | Content-Type: 214 | - application/json 215 | User-Agent: 216 | - neon-client/python version=(0.1.0) 217 | method: GET 218 | uri: https://console.neon.tech/api/v2/projects/winter-smoke-11077351/branches/br-late-rice-a5cf06kb/roles 219 | response: 220 | body: 221 | string: '{"roles":[{"branch_id":"br-late-rice-a5cf06kb","name":"neondb_owner","protected":false,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z"}]}' 222 | headers: 223 | CF-Cache-Status: 224 | - DYNAMIC 225 | CF-RAY: 226 | - 90310b72c8d342a3-EWR 227 | Connection: 228 | - keep-alive 229 | Content-Encoding: 230 | - gzip 231 | Content-Type: 232 | - application/json; charset=utf-8 233 | Date: 234 | - Thu, 16 Jan 2025 21:02:07 GMT 235 | Server: 236 | - cloudflare 237 | Transfer-Encoding: 238 | - chunked 239 | referrer-policy: 240 | - strict-origin-when-cross-origin 241 | strict-transport-security: 242 | - max-age=63072000; includeSubDomains; preload 243 | vary: 244 | - Origin 245 | x-content-type-options: 246 | - nosniff 247 | x-frame-options: 248 | - SAMEORIGIN 249 | x-neon-ret-request-id: 250 | - e3e7d649-1300-4a22-a0fe-5942c8451e6e 251 | status: 252 | code: 200 253 | message: OK 254 | - request: 255 | body: null 256 | headers: 257 | Accept: 258 | - application/json 259 | Accept-Encoding: 260 | - gzip, deflate 261 | Connection: 262 | - keep-alive 263 | Content-Type: 264 | - application/json 265 | User-Agent: 266 | - neon-client/python version=(0.1.0) 267 | method: GET 268 | uri: https://console.neon.tech/api/v2/projects/winter-smoke-11077351/branches/br-late-rice-a5cf06kb/roles/neondb_owner 269 | response: 270 | body: 271 | string: '{"role":{"branch_id":"br-late-rice-a5cf06kb","name":"neondb_owner","protected":false,"created_at":"2025-01-16T21:02:07Z","updated_at":"2025-01-16T21:02:07Z"}}' 272 | headers: 273 | CF-Cache-Status: 274 | - DYNAMIC 275 | CF-RAY: 276 | - 90310b73e982de93-EWR 277 | Connection: 278 | - keep-alive 279 | Content-Encoding: 280 | - gzip 281 | Content-Type: 282 | - application/json; charset=utf-8 283 | Date: 284 | - Thu, 16 Jan 2025 21:02:08 GMT 285 | Server: 286 | - cloudflare 287 | Transfer-Encoding: 288 | - chunked 289 | referrer-policy: 290 | - strict-origin-when-cross-origin 291 | strict-transport-security: 292 | - max-age=63072000; includeSubDomains; preload 293 | vary: 294 | - Origin 295 | x-content-type-options: 296 | - nosniff 297 | x-frame-options: 298 | - SAMEORIGIN 299 | x-neon-ret-request-id: 300 | - 9b10d26a-6349-411c-9efb-5886581c39c6 301 | status: 302 | code: 200 303 | message: OK 304 | version: 1 305 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture 7 | def neon(): 8 | from neon_api import NeonAPI 9 | 10 | return NeonAPI.from_environ() 11 | 12 | 13 | @pytest.fixture 14 | def random_name(*, neon): 15 | def random_name(): 16 | return f"pytest-{randint(0, 10000)}" 17 | 18 | return random_name 19 | 20 | 21 | @pytest.fixture 22 | def ensure_no_projects(*, neon): 23 | def no_projects(): 24 | for project in neon.projects().projects: 25 | neon.project_delete(project.id) 26 | 27 | return no_projects 28 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | import pytest 4 | import neon_api 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def vcr_config(): 9 | return {"filter_headers": ["authorization"]} 10 | 11 | 12 | @pytest.mark.vcr 13 | def test_me(neon): 14 | me = neon.me() 15 | 16 | assert isinstance(me, neon_api.schema.CurrentUserInfoResponse) 17 | assert me.email 18 | 19 | 20 | @pytest.mark.vcr 21 | def test_api_keys(neon, random_name): 22 | key = neon.api_key_create(key_name=random_name()) 23 | 24 | assert len(neon.api_keys()) 25 | 26 | key = neon.api_key_revoke(key.id) 27 | assert key.revoked 28 | 29 | 30 | @pytest.mark.vcr 31 | def test_project(neon, ensure_no_projects, random_name): 32 | # Ensure there are no projects. 33 | ensure_no_projects() 34 | 35 | # Create a project. 36 | project1 = neon.project_create(project={"name": random_name()}).project 37 | assert project1.id 38 | 39 | # Get the project. 40 | project = neon.project(project1.id).project 41 | assert project.id == project1.id 42 | 43 | # Rename the project. 44 | project2 = neon.project_update(project1.id, project={"name": random_name()}).project 45 | 46 | # Ensure that the names are different. 47 | assert project1.name != project2.name 48 | 49 | # Test Projects, ensure each project has an id. 50 | for project in neon.projects().projects: 51 | assert hasattr(project, "id") 52 | 53 | # Test Shared Projects, ensure each project has an id. 54 | for project in neon.projects(shared=True).projects: 55 | assert hasattr(project, "id") 56 | 57 | neon.connection_uri(project.id, "neondb", "neondb_owner", True) 58 | 59 | # Delete the project. 60 | neon.project_delete(project.id) 61 | 62 | 63 | @pytest.mark.vcr 64 | def test_branches(neon, ensure_no_projects, random_name): 65 | # Ensure there are no projects. 66 | ensure_no_projects() 67 | 68 | # Create a project. 69 | project = neon.project_create(project={"name": random_name()}).project 70 | assert project.id 71 | 72 | # import time 73 | 74 | # time.sleep(5) 75 | 76 | # Create a branch. 77 | # branch1 = neon.branch_create(project.id, branch={"name": random_name()}).branch 78 | # assert branch1.id 79 | 80 | # Rename the branch. 81 | # branch2 = neon.branch_update(branch1.id, branch={"name": random_name()}).branch 82 | # Ensure that the names are different. 83 | # assert branch2.name != branch1.name 84 | 85 | # Ensure that the IDs are present. 86 | # for i, branch in neon.branches(project.id).branches: 87 | # if i == 0: 88 | # Only set the first branch as primary. 89 | # neon.branch_set_as_primary(project.id, branch.id) 90 | # pass 91 | 92 | # List branches. 93 | neon.branches(project.id) 94 | # Delete the project. 95 | # neon.branch_delete(project.id, branch_id=neon.branches(project.id).branches[].id) 96 | 97 | 98 | @pytest.mark.vcr 99 | def test_database(neon, ensure_no_projects, random_name): 100 | # Ensure there are no projects. 101 | ensure_no_projects() 102 | 103 | # Create a project. 104 | project = neon.project_create(project={"name": random_name()}).project 105 | assert project.id 106 | 107 | branch = neon.branches(project.id).branches[0] 108 | 109 | # List databases. 110 | databases = neon.databases(project.id, branch.id).databases 111 | assert len(databases) 112 | 113 | # Create a database. 114 | # database = neon.database_create( 115 | # project.id, 116 | # branch.id, 117 | # database={"name": random_name(), "owner_name": "kennethreitz"}, 118 | # ).database 119 | 120 | # Get the Database. 121 | # database = neon.database(project.id, branch.id, database.id).database 122 | # assert database.id 123 | 124 | # # Rename the database. 125 | # database = neon.database_update( 126 | # project.id, 127 | # branch.id, 128 | # database.id, 129 | # database={"name": random_name()}, 130 | # ).database 131 | 132 | 133 | @pytest.mark.vcr 134 | def test_operations(neon, ensure_no_projects, random_name): 135 | # Ensure there are no projects. 136 | ensure_no_projects() 137 | 138 | # Create a project. 139 | project = neon.project_create(project={"name": random_name()}).project 140 | assert project.id 141 | 142 | # Find the associated branch. 143 | branch = neon.branches(project.id).branches[0] 144 | 145 | # List operations. 146 | operations = neon.operations(project.id).operations 147 | assert len(operations) 148 | 149 | # Get the operation. 150 | operation = operations[0] 151 | operation = neon.operation(project.id, operation.id).operation 152 | assert operation.id 153 | 154 | 155 | @pytest.mark.vcr 156 | def test_endpoints(neon, ensure_no_projects, random_name): 157 | # Ensure there are no projects. 158 | ensure_no_projects() 159 | 160 | # Create a project. 161 | project = neon.project_create(project={"name": random_name()}).project 162 | assert project.id 163 | 164 | # List endpoints. 165 | endpoints = neon.endpoints(project.id).endpoints 166 | assert len(endpoints) 167 | 168 | # Get the endpoint. 169 | endpoint = endpoints[0] 170 | endpoint = neon.endpoint(project.id, endpoint.id).endpoint 171 | assert endpoint.id 172 | 173 | 174 | @pytest.mark.vcr 175 | def test_roles(neon, ensure_no_projects, random_name): 176 | # Ensure there are no projects. 177 | ensure_no_projects() 178 | 179 | # Create a project. 180 | project = neon.project_create(project={"name": random_name()}).project 181 | assert project.id 182 | 183 | # Find the associated branch. 184 | branch = neon.branches(project.id).branches[0] 185 | 186 | # List roles. 187 | roles = neon.roles(project.id, branch.id).roles 188 | assert len(roles) 189 | 190 | # Get the role. 191 | role = roles[0] 192 | role = neon.role(project.id, branch.id, role.name).role 193 | assert role.name 194 | --------------------------------------------------------------------------------