├── .coveragerc
├── .github
└── workflows
│ ├── publish.yaml
│ ├── release.yaml
│ └── test.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── .releaserc.json
├── LICENSE
├── README.md
├── docs
├── assets
│ └── nitric-logo.svg
├── index.html
└── nitric
│ ├── api
│ ├── const.html
│ ├── documents.html
│ ├── events.html
│ ├── index.html
│ ├── queues.html
│ ├── secrets.html
│ └── storage.html
│ ├── application.html
│ ├── config
│ ├── default_settings.html
│ └── index.html
│ ├── exception.html
│ ├── faas.html
│ ├── index.html
│ ├── proto
│ ├── index.html
│ ├── nitric
│ │ ├── deploy
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ ├── document
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ ├── error
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ ├── event
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ ├── faas
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ ├── index.html
│ │ ├── queue
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ ├── resource
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ ├── secret
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ │ └── index.html
│ │ └── storage
│ │ │ ├── index.html
│ │ │ └── v1
│ │ │ └── index.html
│ └── validate
│ │ └── index.html
│ ├── resources
│ ├── apis.html
│ ├── base.html
│ ├── buckets.html
│ ├── collections.html
│ ├── index.html
│ ├── queues.html
│ ├── schedules.html
│ ├── secrets.html
│ └── topics.html
│ └── utils.html
├── makefile
├── mypy.ini
├── nitric
├── __init__.py
├── application.py
├── bidi.py
├── channel.py
├── config
│ └── __init__.py
├── context.py
├── exception.py
├── proto
│ ├── __init__.py
│ ├── apis
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── batch
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── deployments
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── http
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── kvstore
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── queues
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── resources
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── schedules
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── secrets
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── sql
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── storage
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ ├── topics
│ │ ├── __init__.py
│ │ └── v1
│ │ │ └── __init__.py
│ └── websockets
│ │ ├── __init__.py
│ │ └── v1
│ │ └── __init__.py
├── py.typed
├── resources
│ ├── __init__.py
│ ├── apis.py
│ ├── buckets.py
│ ├── job.py
│ ├── kv.py
│ ├── queues.py
│ ├── resource.py
│ ├── schedules.py
│ ├── secrets.py
│ ├── sql.py
│ ├── topics.py
│ └── websockets.py
└── utils.py
├── pyproject.toml
├── setup.py
├── tests
├── __init__.py
├── resources
│ ├── __init__.py
│ ├── test_apis.py
│ ├── test_buckets.py
│ ├── test_kv.py
│ ├── test_queues.py
│ ├── test_schedules.py
│ ├── test_secrets.py
│ ├── test_sql.py
│ ├── test_topics.py
│ └── test_websockets.py
├── test__utils.py
├── test_application.py
└── test_exception.py
├── tools
└── apache-2.tmpl
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit =
3 | ./nitric/proto/*
--------------------------------------------------------------------------------
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: Publish release to Pypi
2 |
3 | on:
4 | push:
5 | # run only against tags
6 | tags:
7 | - '*'
8 |
9 | jobs:
10 | publish:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2.3.1
15 | with:
16 | fetch-depth: 0 # needed to retrieve most recent tag
17 | - name: Set up Python '3.11'
18 | uses: actions/setup-python@v2
19 | with:
20 | python-version: '3.11'
21 | - name: Build
22 | run: make build
23 | - name: Publish to PyPI
24 | uses: pypa/gh-action-pypi-publish@master
25 | with:
26 | password: ${{ secrets.PYPI_API_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Production Release
2 | on:
3 | push:
4 | branches:
5 | - main
6 | jobs:
7 | release:
8 | name: semantic-release
9 | runs-on: ubuntu-20.04
10 | outputs:
11 | new-release-published: ${{ steps.semantic-release.outputs.new_release_published }}
12 | new-release-version: ${{ steps.semantic-release.outputs.new_release_version }}
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 | persist-credentials: false
18 | - id: semantic-release
19 | uses: cycjimmy/semantic-release-action@v4
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.NITRIC_BOT_TOKEN }}
22 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | python-version: ['3.11']
15 |
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v2.3.1
19 | with:
20 | fetch-depth: 0 # needed to retrieve most recent tag
21 | - name: Set up Python ${{ matrix.python-version }}
22 | uses: actions/setup-python@v2
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 | - name: Install
26 | run: make install
27 | - name: Check generated sources
28 | run: |
29 | make grpc-client
30 | git add .
31 | git diff --cached --quiet
32 | - name: Run Tox
33 | # Run tox using the version of Python in `PATH`
34 | run: tox -e py
35 | - name: Upload coverage to Codecov
36 | continue-on-error: true
37 | uses: codecov/codecov-action@v4.0.1
38 | with:
39 | fail_ci_if_error: true
40 | token: ${{ secrets.CODECOV_TOKEN }}
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .nitric/
3 | nitric.yaml
4 | nitric.*.yaml
5 | /proto/
6 | /nitric/proto/KeyValue
7 |
8 | # Byte-compiled / optimized / DLL files
9 | __pycache__/
10 | *.py[cod]
11 | *$py.class
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Distribution / packaging
17 | .Python
18 | build/
19 | develop-eggs/
20 | dist/
21 | downloads/
22 | eggs/
23 | .eggs/
24 | lib/
25 | lib64/
26 | parts/
27 | sdist/
28 | var/
29 | wheels/
30 | pip-wheel-metadata/
31 | share/python-wheels/
32 | *.egg-info/
33 | .installed.cfg
34 | *.egg
35 | MANIFEST
36 |
37 | # PyInstaller
38 | # Usually these files are written by a python script from a template
39 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
40 | *.manifest
41 | *.spec
42 |
43 | # Installer logs
44 | pip-log.txt
45 | pip-delete-this-directory.txt
46 |
47 | # Unit test / coverage reports
48 | htmlcov/
49 | .tox/
50 | .nox/
51 | .coverage
52 | .coverage.*
53 | .cache
54 | nosetests.xml
55 | coverage.xml
56 | *.cover
57 | *.py,cover
58 | .hypothesis/
59 | .pytest_cache/
60 | cover/
61 |
62 | # Translations
63 | *.mo
64 | *.pot
65 |
66 | # Django stuff:
67 | *.log
68 | local_settings.py
69 | db.sqlite3
70 | db.sqlite3-journal
71 |
72 | # Flask stuff:
73 | instance/
74 | .webassets-cache
75 |
76 | # Scrapy stuff:
77 | .scrapy
78 |
79 | # Sphinx documentation
80 | docs/_build/
81 |
82 | # PyBuilder
83 | .pybuilder/
84 | target/
85 |
86 | # Jupyter Notebook
87 | .ipynb_checkpoints
88 |
89 | # IPython
90 | profile_default/
91 | ipython_config.py
92 |
93 | # pyenv
94 | # For a library or package, you might want to ignore these files since the code is
95 | # intended to run in multiple environments; otherwise, check them in:
96 | # .python-version
97 |
98 | # pipenv
99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
102 | # install all needed dependencies.
103 | #Pipfile.lock
104 |
105 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
106 | __pypackages__/
107 |
108 | # Celery stuff
109 | celerybeat-schedule
110 | celerybeat.pid
111 |
112 | # SageMath parsed files
113 | *.sage.py
114 |
115 | # Environments
116 | .env
117 | .venv
118 | env/
119 | venv/
120 | ENV/
121 | env.bak/
122 | venv.bak/
123 |
124 | # Spyder project settings
125 | .spyderproject
126 | .spyproject
127 |
128 | # Rope project settings
129 | .ropeproject
130 |
131 | # mkdocs documentation
132 | /site
133 |
134 | # mypy
135 | .mypy_cache/
136 | .dmypy.json
137 | dmypy.json
138 |
139 | # Pyre type checker
140 | .pyre/
141 |
142 | # pytype static type analyzer
143 | .pytype/
144 |
145 | # Cython debug symbols
146 | cython_debug/
147 |
148 |
149 | .vscode/
150 |
151 | contracts/
152 |
153 | testproj/
154 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/ambv/black
3 | rev: stable
4 | hooks:
5 | - id: black
6 | exclude: ^(nitric/proto)/
7 | - repo: https://github.com/pycqa/flake8
8 | rev: 3.7.9
9 | hooks:
10 | - id: flake8
11 | exclude: ^(venv|tests|build|dist|nitric/proto)/
12 | - repo: https://github.com/pycqa/pydocstyle
13 | rev: 6.0.0
14 | hooks:
15 | - id: pydocstyle
16 | args:
17 | - --ignore=D100, D105, D203, D212, D415
18 | exclude: ^(venv|tests|build|dist|nitric/proto)/
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.11.0
2 |
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "branches": ["main"],
3 | "plugins": [
4 | "@semantic-release/commit-analyzer",
5 | "@semantic-release/release-notes-generator",
6 | "@semantic-release/github"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Build nitric applications with Python
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | The Python SDK supports the use of the [Nitric](https://nitric.io) framework with Python 3.11+. For more information check out the main [Nitric repo](https://github.com/nitrictech/nitric).
26 |
27 | Python SDKs provide an infrastructure-from-code style that lets you define resources in code. You can also write the functions that support the logic behind APIs, subscribers and schedules.
28 |
29 | You can request the type of access you need to resources such as publishing for topics, without dealing directly with IAM or policy documents.
30 |
31 | - Reference Documentation: https://nitric.io/docs/reference/python
32 | - Guides: https://nitric.io/docs/guides/python
33 |
34 | ## Usage
35 |
36 | ### Starting a new project
37 |
38 | Install the [Nitric CLI](https://nitric.io/docs/getting-started/installation), then generate your project:
39 |
40 | ```bash
41 | nitric new hello-world py-starter
42 | ```
43 |
44 | ### Add to an existing project
45 |
46 | First of all, you need to install the library:
47 |
48 | **pip**
49 |
50 | ```bash
51 | pip3 install nitric
52 | ```
53 |
54 | **pipenv**
55 |
56 | ```
57 | pipenv install nitric
58 | ```
59 |
60 | Then you're able to import the library and create cloud resources:
61 |
62 | ```python
63 | from nitric.resources import api, bucket
64 | from nitric.application import Nitric
65 | from nitric.context import HttpContext
66 |
67 | publicApi = api("public")
68 | uploads = bucket("uploads").allow("write")
69 |
70 | @publicApi.get("/upload")
71 | async def upload(ctx: HttpContext):
72 | photo = uploads.file("images/photo.jpg")
73 |
74 | url = await photo.upload_url()
75 |
76 | ctx.res.body = {"url": url}
77 |
78 | Nitric.run()
79 | ```
80 |
81 | ## Learn more
82 |
83 | Learn more by checking out the [Nitric documentation](https://nitric.io/docs).
84 |
85 | ## Get in touch:
86 |
87 | - Join us on [Discord](https://nitric.io/chat)
88 |
89 | - Ask questions in [GitHub discussions](https://github.com/nitrictech/nitric/discussions)
90 |
91 | - Find us on [Twitter](https://twitter.com/nitric_io)
92 |
93 | - Send us an [email](mailto:maintainers@nitric.io)
94 |
--------------------------------------------------------------------------------
/docs/assets/nitric-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | If you are not redirected, click here .
7 |
8 |
--------------------------------------------------------------------------------
/docs/nitric/api/const.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.api.const API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.api.const
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 | # The maximum number of parent collections a sub-collection can have.
49 | # This is implemented in the Membrane, but reinforced here for immediate exceptions without a server connection.
50 | MAX_SUB_COLLECTION_DEPTH = 1
51 |
52 |
53 |
55 |
57 |
59 |
61 |
62 |
75 |
76 |
79 |
80 |
--------------------------------------------------------------------------------
/docs/nitric/config/default_settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.config.default_settings API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.config.default_settings
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 | import os
48 |
49 | # Provides a set of default settings that env vars can replace
50 | # Nitric Membrane Service Address
51 | SERVICE_BIND = os.environ.get('SERVICE_ADDRESS', '127.0.0.1:50051');
52 |
53 |
54 |
56 |
58 |
60 |
62 |
63 |
76 |
77 |
80 |
81 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/deploy/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.deploy API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.deploy
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/document/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.document API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.document
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/error/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.error API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.error
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/event/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.event API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.event
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/faas/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.faas API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.faas
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/queue/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.queue API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.queue
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/resource/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.resource API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.resource
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/secret/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.secret API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.secret
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/docs/nitric/proto/nitric/storage/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | nitric.proto.nitric.storage API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Module nitric.proto.nitric.storage
23 |
24 |
25 |
26 |
27 | Expand source code
28 |
29 | #
30 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
31 | #
32 | # This file is part of Nitric Python 3 SDK.
33 | # See https://github.com/nitrictech/python-sdk for further info.
34 | #
35 | # Licensed under the Apache License, Version 2.0 (the "License");
36 | # you may not use this file except in compliance with the License.
37 | # You may obtain a copy of the License at
38 | #
39 | # http://www.apache.org/licenses/LICENSE-2.0
40 | #
41 | # Unless required by applicable law or agreed to in writing, software
42 | # distributed under the License is distributed on an "AS IS" BASIS,
43 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
44 | # See the License for the specific language governing permissions and
45 | # limitations under the License.
46 | #
47 |
48 |
49 |
58 |
60 |
62 |
64 |
65 |
83 |
84 |
87 |
88 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | install:
2 | @echo Installing Project Dependencies
3 | @python3 -m pip install -e .[dev]
4 | @pre-commit install
5 | @rm -rf ./.tox
6 |
7 | .PHONY: docs clean license
8 |
9 | docs:
10 | @echo Generating SDK Documentation
11 | @rm -rf docs/nitric
12 | @pdoc3 -f --html -o docs nitric
13 |
14 | clean:
15 | @echo Cleaning Build Artefacts
16 | @rm -rf ./.eggs
17 | @rm -rf ./build
18 | @rm -rf ./dist
19 |
20 | test:
21 | @echo Running Tox tests
22 | @tox -e py
23 |
24 | NITRIC_VERSION := 1.14.0
25 |
26 | download-local:
27 | @rm -r ./proto/nitric
28 | @mkdir ./proto/nitric
29 | @cp -r ${NITRIC_CORE_HOME}/nitric/proto ./proto/nitric
30 |
31 | download:
32 | @mkdir -p ./proto/
33 | @curl -L https://github.com/nitrictech/nitric/releases/download/v${NITRIC_VERSION}/proto.tgz -o ./proto/nitric.tgz
34 | @cd ./proto && tar xvzf nitric.tgz
35 | @cd ../
36 | @rm ./proto/nitric.tgz
37 |
38 | OUTPUT="."
39 | CONTRACTS="./proto"
40 |
41 | grpc-client: install download generate-proto
42 |
43 | generate-proto:
44 | @echo Generating Proto Sources
45 | @ rm -rf $(OUTPUT)/nitric/proto
46 | @echo $(OUTPUT)
47 | @mkdir -p $(OUTPUT)
48 | @python3 -m grpc_tools.protoc -I $(CONTRACTS) --python_betterproto_out=$(OUTPUT) ./$(CONTRACTS)/nitric/proto/*/*/*.proto
49 | @rm ./__init__.py
50 |
51 | license:
52 | @echo Applying Apache 2 header to source files
53 | @licenseheaders -t tools/apache-2.tmpl -o "Nitric Technologies Pty Ltd" -y 2021 -n "Nitric Python 3 SDK" -u "https://github.com/nitrictech/python-sdk" -d nitric
54 | @licenseheaders -t tools/apache-2.tmpl -o "Nitric Technologies Pty Ltd" -y 2021 -n "Nitric Python 3 SDK" -u "https://github.com/nitrictech/python-sdk" -d tests
55 | @licenseheaders -t tools/apache-2.tmpl -o "Nitric Technologies Pty Ltd" -y 2021 -n "Nitric Python 3 SDK" -u "https://github.com/nitrictech/python-sdk" -d tools
56 |
57 | build: clean grpc-client license docs
58 | @echo Building sdist and wheel
59 | @python3 setup.py sdist bdist_wheel
60 |
61 | distribute: build
62 | @echo Uploading to pypi
63 | @python3 -m twine upload --repository pypi dist/*
64 |
65 | distribute-test: build
66 | @echo Uploading to testpypi
67 | @python3 -m twine upload --repository testpypi dist/*
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 |
3 |
4 | [mypy-nitric]
5 | disallow_untyped_defs = True
--------------------------------------------------------------------------------
/nitric/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | """Nitric Python SDK API Documentation. See: https://nitric.io/docs?lang=python for full framework documentation."""
20 |
--------------------------------------------------------------------------------
/nitric/application.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | import asyncio
20 | from typing import Any, Dict, List, Type, TypeVar
21 |
22 | from nitric.context import FunctionServer
23 | from nitric.exception import NitricUnavailableException
24 |
25 | BT = TypeVar("BT")
26 |
27 |
28 | class Nitric:
29 | """Represents a nitric app."""
30 |
31 | _has_run = False
32 |
33 | _workers: List[FunctionServer] = []
34 | _cache: Dict[str, Dict[str, Any]] = {
35 | "api": {},
36 | "bucket": {},
37 | "topic": {},
38 | "secret": {},
39 | "queue": {},
40 | "collection": {},
41 | "websocket": {},
42 | "keyvaluestore": {},
43 | "oidcsecuritydefinition": {},
44 | "sql": {},
45 | "job": {},
46 | "jobdefinition": {},
47 | }
48 |
49 | @classmethod
50 | def _register_worker(cls, srv: FunctionServer):
51 | """Register a worker for this application."""
52 | cls._workers.append(srv)
53 |
54 | @classmethod
55 | def _create_resource(cls, resource: Type[BT], name: str, *args: Any, **kwargs: Any) -> BT:
56 | try:
57 | resource_type = resource.__name__.lower()
58 | cached_resources = cls._cache.get(resource_type)
59 | if cached_resources is None or cached_resources.get(name) is None:
60 | cls._cache[resource_type][name] = resource.make(name, *args, **kwargs) # type: ignore
61 |
62 | return cls._cache[resource_type][name]
63 | except ConnectionRefusedError as cre:
64 | raise NitricUnavailableException(
65 | "The nitric server may not be running or the host/port is inaccessible"
66 | ) from cre
67 |
68 | @classmethod
69 | def has_run(cls) -> bool:
70 | """
71 | Check if the Nitric application has been started.
72 |
73 | Returns:
74 | bool: True if the Nitric application has been started, False otherwise.
75 | """
76 | return cls._has_run
77 |
78 | @classmethod
79 | def run(cls) -> None:
80 | """
81 | Start the nitric application.
82 |
83 | This will execute in an existing event loop if there is one, otherwise it will attempt to create its own.
84 | """
85 | if cls._has_run:
86 | print("The Nitric application has already been started, Nitric.run() should only be called once.")
87 | cls._has_run = True
88 | try:
89 | try:
90 | loop = asyncio.get_running_loop()
91 | except RuntimeError:
92 | loop = asyncio.get_event_loop()
93 |
94 | loop.run_until_complete(asyncio.gather(*[wkr.start() for wkr in cls._workers]))
95 | except KeyboardInterrupt:
96 |
97 | print("\nexiting")
98 | except ConnectionRefusedError as cre:
99 | raise NitricUnavailableException(
100 | 'If you\'re running locally use "nitric start" or "nitric run" to start your application'
101 | ) from cre
102 |
--------------------------------------------------------------------------------
/nitric/bidi.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | import asyncio
20 | from typing import Generic, List, TypeVar
21 |
22 | T = TypeVar("T")
23 |
24 |
25 | class AsyncNotifierList(Generic[T]):
26 | """An async iterable that notifies when new items are added."""
27 |
28 | def __init__(self):
29 | """Create a new AsyncNotifierList."""
30 | self.items: List[T] = [] # type: ignore
31 | self.new_item_event: asyncio.Event = asyncio.Event() # type: ignore
32 |
33 | async def add_item(self, item: T) -> None:
34 | """Add a new item to the list."""
35 | self.items.append(item)
36 | self.new_item_event.set()
37 |
38 | def __aiter__(self):
39 | return self
40 |
41 | async def __anext__(self):
42 | while not self.items:
43 | await self.new_item_event.wait() # Wait for an item to be added
44 | item = self.items.pop(0)
45 | if not self.items:
46 | self.new_item_event.clear() # Reset the event if there are no more items
47 | return item
48 |
--------------------------------------------------------------------------------
/nitric/channel.py:
--------------------------------------------------------------------------------
1 | import atexit
2 | import re
3 | from urllib.parse import urlparse
4 | from grpclib.client import Channel
5 |
6 | from nitric.application import Nitric
7 | from nitric.config import settings
8 | from nitric.exception import NitricNotRunningException
9 |
10 |
11 | def format_url(url: str):
12 | """Add the default http scheme prefix to urls without one."""
13 | if not re.match("^((?:http|ftp|https):)?//", url.lower()):
14 | return "http://{0}".format(url)
15 | return url
16 |
17 |
18 | class ChannelManager:
19 | """A singleton class to manage the gRPC channel."""
20 |
21 | channel = None
22 |
23 | @classmethod
24 | def get_channel(cls) -> Channel:
25 | """Return the channel instance."""
26 |
27 | if cls.channel is None:
28 | cls._create_channel()
29 | return cls.channel # type: ignore
30 |
31 | @classmethod
32 | def _create_channel(cls):
33 | """Create a new channel instance."""
34 |
35 | channel_url = urlparse(format_url(settings.SERVICE_ADDRESS))
36 | cls.channel = Channel(host=channel_url.hostname, port=channel_url.port)
37 | atexit.register(cls._close_channel)
38 |
39 | @classmethod
40 | def _close_channel(cls):
41 | """Close the channel instance."""
42 |
43 | if cls.channel is not None:
44 | cls.channel.close()
45 | cls.channel = None
46 |
47 | # If the program exits without calling Nitric.run(), it may have been a mistake.
48 | if not Nitric.has_run():
49 | print(
50 | "WARNING: The Nitric application was not started. "
51 | "If you intended to start the application, call Nitric.run() before exiting."
52 | )
53 |
--------------------------------------------------------------------------------
/nitric/config/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | """Nitric SDK Configuration Settings."""
20 |
21 | import os
22 |
23 |
24 | class Settings:
25 | """Nitric default and env settings helper class."""
26 |
27 | def __init__(self):
28 | """Construct a new Nitric settings helper object."""
29 | self.SERVICE_ADDRESS = os.environ.get("SERVICE_ADDRESS", "127.0.0.1:50051")
30 |
31 |
32 | settings = Settings()
33 |
--------------------------------------------------------------------------------
/nitric/exception.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from typing import Callable, Optional, Union
20 |
21 | from grpclib import GRPCError
22 |
23 |
24 | class NitricServiceException(Exception):
25 | """Base exception for all errors returned by Nitric API methods."""
26 |
27 | pass
28 |
29 |
30 | class AbortedException(NitricServiceException):
31 | """The operation was aborted, typically due to a concurrency issue such as a transaction abort."""
32 |
33 | pass
34 |
35 |
36 | class AlreadyExistsException(NitricServiceException):
37 | """The entity that a client attempted to create (e.g., file or directory) already exists."""
38 |
39 | pass
40 |
41 |
42 | class CancelledException(NitricServiceException):
43 | """The operation was cancelled, typically by the caller."""
44 |
45 | pass
46 |
47 |
48 | class DataLossException(NitricServiceException):
49 | """Unrecoverable data loss or corruption."""
50 |
51 | pass
52 |
53 |
54 | class DeadlineExceededException(NitricServiceException):
55 | """The deadline expired before the operation could complete."""
56 |
57 | pass
58 |
59 |
60 | class FailedPreconditionException(NitricServiceException):
61 | """
62 | The operation was rejected because the system is not in a state required for the operation's execution.
63 |
64 | For example, the document collection to be deleted is not empty.
65 | """
66 |
67 | pass
68 |
69 |
70 | class InternalException(NitricServiceException):
71 | """Internal errors."""
72 |
73 | pass
74 |
75 |
76 | class InvalidArgumentException(NitricServiceException):
77 | """
78 | The client specified an invalid argument.
79 |
80 | Note that this differs from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are problematic
81 | regardless of the state of the system (e.g., a malformed file name).
82 | """
83 |
84 | pass
85 |
86 |
87 | class OutOfRangeException(NitricServiceException):
88 | """
89 | The operation was attempted past the valid range.
90 |
91 | E.g. reading past the end of a file.
92 | """
93 |
94 | pass
95 |
96 |
97 | class NotFoundException(NitricServiceException):
98 | """Some requested entity was not found."""
99 |
100 | pass
101 |
102 |
103 | class PermissionDeniedException(NitricServiceException):
104 | """The caller does not have permission to execute the specified operation."""
105 |
106 | pass
107 |
108 |
109 | class ResourceExhaustedException(NitricServiceException):
110 | """Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space."""
111 |
112 | pass
113 |
114 |
115 | class UnauthenticatedException(NitricServiceException):
116 | """The request does not have valid authentication credentials for the operation."""
117 |
118 | pass
119 |
120 |
121 | class UnavailableException(NitricServiceException):
122 | """
123 | The service is currently unavailable.
124 |
125 | This is most likely a transient condition, which can be corrected by retrying with a backoff.
126 | """
127 |
128 | pass
129 |
130 |
131 | class UnimplementedException(NitricServiceException):
132 | """
133 | The operation is not implemented or is not supported/enabled in this service.
134 |
135 | May appear when using an older version of the Membrane with a newer SDK.
136 | """
137 |
138 | pass
139 |
140 |
141 | class UnknownException(NitricServiceException):
142 | """Unknown error."""
143 |
144 | pass
145 |
146 |
147 | class NitricResourceException(Exception):
148 | """Illegal nitric resource creation."""
149 |
150 | pass
151 |
152 |
153 | class NitricUnavailableException(Exception):
154 | """Unable to connect to a nitric server."""
155 |
156 | def __init__(self, message: str):
157 | super().__init__("Unable to connect to nitric server." + (" " + message if message else ""))
158 |
159 |
160 | class NitricNotRunningException(Exception):
161 | """The Nitric application wasn't started before the program exited."""
162 |
163 | def __init__(self):
164 | super().__init__("The Nitric application was not started, call Nitric.run() to start the application.")
165 |
166 |
167 | def exception_from_grpc_error(error: GRPCError):
168 | """Translate a gRPC error to a nitric api exception."""
169 | return exception_from_grpc_code(error.status.value, error.message or "")
170 |
171 |
172 | def exception_from_grpc_code(code: int, message: Optional[str] = None):
173 | """
174 | Return a new instance of the appropriate exception for the given status code.
175 |
176 | If an unknown or unexpected status code value is provided an UnknownException will be returned.
177 | """
178 | if code not in _exception_code_map:
179 | return UnknownException()
180 |
181 | return _exception_code_map[code](message)
182 |
183 |
184 | _zero_code_exception: Callable[[Union[str, None]], Exception] = lambda message: Exception(
185 | "Error returned with status 0, which is a success status"
186 | )
187 |
188 | # Map of gRPC status codes to the appropriate exception class.
189 | _exception_code_map = {
190 | 0: _zero_code_exception,
191 | 1: CancelledException,
192 | 2: UnknownException,
193 | 3: InvalidArgumentException,
194 | 4: DeadlineExceededException,
195 | 5: NotFoundException,
196 | 6: AlreadyExistsException,
197 | 7: PermissionDeniedException,
198 | 8: ResourceExhaustedException,
199 | 9: FailedPreconditionException,
200 | 10: AbortedException,
201 | 11: OutOfRangeException,
202 | 12: UnimplementedException,
203 | 13: InternalException,
204 | 14: UnavailableException,
205 | 15: DataLossException,
206 | 16: UnauthenticatedException,
207 | }
208 |
--------------------------------------------------------------------------------
/nitric/proto/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/apis/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/apis/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/batch/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/batch/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/batch/v1/__init__.py:
--------------------------------------------------------------------------------
1 | # Generated by the protocol buffer compiler. DO NOT EDIT!
2 | # sources: nitric/proto/batch/v1/batch.proto
3 | # plugin: python-betterproto
4 | # This file has been @generated
5 |
6 | from dataclasses import dataclass
7 | from typing import (
8 | TYPE_CHECKING,
9 | AsyncIterable,
10 | AsyncIterator,
11 | Dict,
12 | Iterable,
13 | Optional,
14 | Union,
15 | )
16 |
17 | import betterproto
18 | import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf
19 | import grpclib
20 | from betterproto.grpc.grpclib_server import ServiceBase
21 |
22 |
23 | if TYPE_CHECKING:
24 | import grpclib.server
25 | from betterproto.grpc.grpclib_client import MetadataLike
26 | from grpclib.metadata import Deadline
27 |
28 |
29 | @dataclass(eq=False, repr=False)
30 | class ClientMessage(betterproto.Message):
31 | id: str = betterproto.string_field(1)
32 | """globally unique ID of the request/response pair"""
33 |
34 | registration_request: "RegistrationRequest" = betterproto.message_field(
35 | 2, group="content"
36 | )
37 | """Register a handler for a job"""
38 |
39 | job_response: "JobResponse" = betterproto.message_field(3, group="content")
40 | """Handle a job submission"""
41 |
42 |
43 | @dataclass(eq=False, repr=False)
44 | class JobRequest(betterproto.Message):
45 | job_name: str = betterproto.string_field(1)
46 | data: "JobData" = betterproto.message_field(2)
47 |
48 |
49 | @dataclass(eq=False, repr=False)
50 | class JobData(betterproto.Message):
51 | struct: "betterproto_lib_google_protobuf.Struct" = betterproto.message_field(
52 | 1, group="data"
53 | )
54 |
55 |
56 | @dataclass(eq=False, repr=False)
57 | class JobResponse(betterproto.Message):
58 | success: bool = betterproto.bool_field(1)
59 | """Mark if the job was successfully processed"""
60 |
61 |
62 | @dataclass(eq=False, repr=False)
63 | class RegistrationRequest(betterproto.Message):
64 | job_name: str = betterproto.string_field(1)
65 | requirements: "JobResourceRequirements" = betterproto.message_field(2)
66 | """Register with default requirements"""
67 |
68 |
69 | @dataclass(eq=False, repr=False)
70 | class RegistrationResponse(betterproto.Message):
71 | pass
72 |
73 |
74 | @dataclass(eq=False, repr=False)
75 | class JobResourceRequirements(betterproto.Message):
76 | cpus: float = betterproto.float_field(1)
77 | """The number of CPUs to allocate for the job"""
78 |
79 | memory: int = betterproto.int64_field(2)
80 | """The amount of memory to allocate for the job"""
81 |
82 | gpus: int = betterproto.int64_field(3)
83 | """The number of GPUs to allocate for the job"""
84 |
85 |
86 | @dataclass(eq=False, repr=False)
87 | class ServerMessage(betterproto.Message):
88 | """
89 | ServerMessage is the message sent from the nitric server to the service
90 | """
91 |
92 | id: str = betterproto.string_field(1)
93 | """globally unique ID of the request/response pair"""
94 |
95 | registration_response: "RegistrationResponse" = betterproto.message_field(
96 | 2, group="content"
97 | )
98 | """
99 |
100 | """
101 |
102 | job_request: "JobRequest" = betterproto.message_field(3, group="content")
103 | """Request to a job handler"""
104 |
105 |
106 | @dataclass(eq=False, repr=False)
107 | class JobSubmitRequest(betterproto.Message):
108 | job_name: str = betterproto.string_field(1)
109 | """The name of the job that should handle the data"""
110 |
111 | data: "JobData" = betterproto.message_field(2)
112 | """The data to be processed by the job"""
113 |
114 |
115 | @dataclass(eq=False, repr=False)
116 | class JobSubmitResponse(betterproto.Message):
117 | pass
118 |
119 |
120 | class JobStub(betterproto.ServiceStub):
121 | async def handle_job(
122 | self,
123 | client_message_iterator: Union[
124 | AsyncIterable["ClientMessage"], Iterable["ClientMessage"]
125 | ],
126 | *,
127 | timeout: Optional[float] = None,
128 | deadline: Optional["Deadline"] = None,
129 | metadata: Optional["MetadataLike"] = None
130 | ) -> AsyncIterator["ServerMessage"]:
131 | async for response in self._stream_stream(
132 | "/nitric.proto.batch.v1.Job/HandleJob",
133 | client_message_iterator,
134 | ClientMessage,
135 | ServerMessage,
136 | timeout=timeout,
137 | deadline=deadline,
138 | metadata=metadata,
139 | ):
140 | yield response
141 |
142 |
143 | class BatchStub(betterproto.ServiceStub):
144 | async def submit_job(
145 | self,
146 | job_submit_request: "JobSubmitRequest",
147 | *,
148 | timeout: Optional[float] = None,
149 | deadline: Optional["Deadline"] = None,
150 | metadata: Optional["MetadataLike"] = None
151 | ) -> "JobSubmitResponse":
152 | return await self._unary_unary(
153 | "/nitric.proto.batch.v1.Batch/SubmitJob",
154 | job_submit_request,
155 | JobSubmitResponse,
156 | timeout=timeout,
157 | deadline=deadline,
158 | metadata=metadata,
159 | )
160 |
161 |
162 | class JobBase(ServiceBase):
163 | async def handle_job(
164 | self, client_message_iterator: AsyncIterator["ClientMessage"]
165 | ) -> AsyncIterator["ServerMessage"]:
166 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
167 | yield ServerMessage()
168 |
169 | async def __rpc_handle_job(
170 | self, stream: "grpclib.server.Stream[ClientMessage, ServerMessage]"
171 | ) -> None:
172 | request = stream.__aiter__()
173 | await self._call_rpc_handler_server_stream(
174 | self.handle_job,
175 | stream,
176 | request,
177 | )
178 |
179 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
180 | return {
181 | "/nitric.proto.batch.v1.Job/HandleJob": grpclib.const.Handler(
182 | self.__rpc_handle_job,
183 | grpclib.const.Cardinality.STREAM_STREAM,
184 | ClientMessage,
185 | ServerMessage,
186 | ),
187 | }
188 |
189 |
190 | class BatchBase(ServiceBase):
191 | async def submit_job(
192 | self, job_submit_request: "JobSubmitRequest"
193 | ) -> "JobSubmitResponse":
194 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
195 |
196 | async def __rpc_submit_job(
197 | self, stream: "grpclib.server.Stream[JobSubmitRequest, JobSubmitResponse]"
198 | ) -> None:
199 | request = await stream.recv_message()
200 | response = await self.submit_job(request)
201 | await stream.send_message(response)
202 |
203 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
204 | return {
205 | "/nitric.proto.batch.v1.Batch/SubmitJob": grpclib.const.Handler(
206 | self.__rpc_submit_job,
207 | grpclib.const.Cardinality.UNARY_UNARY,
208 | JobSubmitRequest,
209 | JobSubmitResponse,
210 | ),
211 | }
212 |
--------------------------------------------------------------------------------
/nitric/proto/deployments/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/deployments/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/http/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/http/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/http/v1/__init__.py:
--------------------------------------------------------------------------------
1 | # Generated by the protocol buffer compiler. DO NOT EDIT!
2 | # sources: nitric/proto/http/v1/http.proto
3 | # plugin: python-betterproto
4 | # This file has been @generated
5 |
6 | from dataclasses import dataclass
7 | from typing import (
8 | TYPE_CHECKING,
9 | AsyncIterable,
10 | AsyncIterator,
11 | Dict,
12 | Iterable,
13 | Optional,
14 | Union,
15 | )
16 |
17 | import betterproto
18 | import grpclib
19 | from betterproto.grpc.grpclib_server import ServiceBase
20 |
21 |
22 | if TYPE_CHECKING:
23 | import grpclib.server
24 | from betterproto.grpc.grpclib_client import MetadataLike
25 | from grpclib.metadata import Deadline
26 |
27 |
28 | @dataclass(eq=False, repr=False)
29 | class ClientMessage(betterproto.Message):
30 | request: "HttpProxyRequest" = betterproto.message_field(1)
31 | """Details of the HTTP server to proxy"""
32 |
33 |
34 | @dataclass(eq=False, repr=False)
35 | class ServerMessage(betterproto.Message):
36 | pass
37 |
38 |
39 | @dataclass(eq=False, repr=False)
40 | class HttpProxyRequest(betterproto.Message):
41 | host: str = betterproto.string_field(1)
42 | """The address the server can be accessed on"""
43 |
44 |
45 | class HttpStub(betterproto.ServiceStub):
46 | async def proxy(
47 | self,
48 | client_message_iterator: Union[
49 | AsyncIterable["ClientMessage"], Iterable["ClientMessage"]
50 | ],
51 | *,
52 | timeout: Optional[float] = None,
53 | deadline: Optional["Deadline"] = None,
54 | metadata: Optional["MetadataLike"] = None
55 | ) -> AsyncIterator["ServerMessage"]:
56 | async for response in self._stream_stream(
57 | "/nitric.proto.http.v1.Http/Proxy",
58 | client_message_iterator,
59 | ClientMessage,
60 | ServerMessage,
61 | timeout=timeout,
62 | deadline=deadline,
63 | metadata=metadata,
64 | ):
65 | yield response
66 |
67 |
68 | class HttpBase(ServiceBase):
69 | async def proxy(
70 | self, client_message_iterator: AsyncIterator["ClientMessage"]
71 | ) -> AsyncIterator["ServerMessage"]:
72 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
73 | yield ServerMessage()
74 |
75 | async def __rpc_proxy(
76 | self, stream: "grpclib.server.Stream[ClientMessage, ServerMessage]"
77 | ) -> None:
78 | request = stream.__aiter__()
79 | await self._call_rpc_handler_server_stream(
80 | self.proxy,
81 | stream,
82 | request,
83 | )
84 |
85 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
86 | return {
87 | "/nitric.proto.http.v1.Http/Proxy": grpclib.const.Handler(
88 | self.__rpc_proxy,
89 | grpclib.const.Cardinality.STREAM_STREAM,
90 | ClientMessage,
91 | ServerMessage,
92 | ),
93 | }
94 |
--------------------------------------------------------------------------------
/nitric/proto/kvstore/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/kvstore/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/queues/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/queues/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/resources/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/resources/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/schedules/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/schedules/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/schedules/v1/__init__.py:
--------------------------------------------------------------------------------
1 | # Generated by the protocol buffer compiler. DO NOT EDIT!
2 | # sources: nitric/proto/schedules/v1/schedules.proto
3 | # plugin: python-betterproto
4 | # This file has been @generated
5 |
6 | from dataclasses import dataclass
7 | from typing import (
8 | TYPE_CHECKING,
9 | AsyncIterable,
10 | AsyncIterator,
11 | Dict,
12 | Iterable,
13 | Optional,
14 | Union,
15 | )
16 |
17 | import betterproto
18 | import grpclib
19 | from betterproto.grpc.grpclib_server import ServiceBase
20 |
21 |
22 | if TYPE_CHECKING:
23 | import grpclib.server
24 | from betterproto.grpc.grpclib_client import MetadataLike
25 | from grpclib.metadata import Deadline
26 |
27 |
28 | @dataclass(eq=False, repr=False)
29 | class ClientMessage(betterproto.Message):
30 | """ClientMessages are sent from the service to the nitric server"""
31 |
32 | id: str = betterproto.string_field(1)
33 | """globally unique ID of the request/response pair"""
34 |
35 | registration_request: "RegistrationRequest" = betterproto.message_field(
36 | 2, group="content"
37 | )
38 | """Register new a schedule"""
39 |
40 | interval_response: "IntervalResponse" = betterproto.message_field(
41 | 3, group="content"
42 | )
43 | """
44 | Response to a schedule interval (i.e. response from callback function)
45 | """
46 |
47 |
48 | @dataclass(eq=False, repr=False)
49 | class IntervalRequest(betterproto.Message):
50 | schedule_name: str = betterproto.string_field(1)
51 |
52 |
53 | @dataclass(eq=False, repr=False)
54 | class ServerMessage(betterproto.Message):
55 | """ServerMessages are sent from the nitric server to the service"""
56 |
57 | id: str = betterproto.string_field(1)
58 | """globally unique ID of the request/response pair"""
59 |
60 | registration_response: "RegistrationResponse" = betterproto.message_field(
61 | 2, group="content"
62 | )
63 | """Response to a schedule subscription request"""
64 |
65 | interval_request: "IntervalRequest" = betterproto.message_field(3, group="content")
66 | """A schedule interval trigger request (i.e. call the callback)"""
67 |
68 |
69 | @dataclass(eq=False, repr=False)
70 | class RegistrationRequest(betterproto.Message):
71 | schedule_name: str = betterproto.string_field(1)
72 | every: "ScheduleEvery" = betterproto.message_field(10, group="cadence")
73 | cron: "ScheduleCron" = betterproto.message_field(11, group="cadence")
74 |
75 |
76 | @dataclass(eq=False, repr=False)
77 | class ScheduleEvery(betterproto.Message):
78 | rate: str = betterproto.string_field(1)
79 |
80 |
81 | @dataclass(eq=False, repr=False)
82 | class ScheduleCron(betterproto.Message):
83 | expression: str = betterproto.string_field(1)
84 |
85 |
86 | @dataclass(eq=False, repr=False)
87 | class RegistrationResponse(betterproto.Message):
88 | pass
89 |
90 |
91 | @dataclass(eq=False, repr=False)
92 | class IntervalResponse(betterproto.Message):
93 | pass
94 |
95 |
96 | class SchedulesStub(betterproto.ServiceStub):
97 | async def schedule(
98 | self,
99 | client_message_iterator: Union[
100 | AsyncIterable["ClientMessage"], Iterable["ClientMessage"]
101 | ],
102 | *,
103 | timeout: Optional[float] = None,
104 | deadline: Optional["Deadline"] = None,
105 | metadata: Optional["MetadataLike"] = None
106 | ) -> AsyncIterator["ServerMessage"]:
107 | async for response in self._stream_stream(
108 | "/nitric.proto.schedules.v1.Schedules/Schedule",
109 | client_message_iterator,
110 | ClientMessage,
111 | ServerMessage,
112 | timeout=timeout,
113 | deadline=deadline,
114 | metadata=metadata,
115 | ):
116 | yield response
117 |
118 |
119 | class SchedulesBase(ServiceBase):
120 | async def schedule(
121 | self, client_message_iterator: AsyncIterator["ClientMessage"]
122 | ) -> AsyncIterator["ServerMessage"]:
123 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
124 | yield ServerMessage()
125 |
126 | async def __rpc_schedule(
127 | self, stream: "grpclib.server.Stream[ClientMessage, ServerMessage]"
128 | ) -> None:
129 | request = stream.__aiter__()
130 | await self._call_rpc_handler_server_stream(
131 | self.schedule,
132 | stream,
133 | request,
134 | )
135 |
136 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
137 | return {
138 | "/nitric.proto.schedules.v1.Schedules/Schedule": grpclib.const.Handler(
139 | self.__rpc_schedule,
140 | grpclib.const.Cardinality.STREAM_STREAM,
141 | ClientMessage,
142 | ServerMessage,
143 | ),
144 | }
145 |
--------------------------------------------------------------------------------
/nitric/proto/secrets/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/secrets/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/secrets/v1/__init__.py:
--------------------------------------------------------------------------------
1 | # Generated by the protocol buffer compiler. DO NOT EDIT!
2 | # sources: nitric/proto/secrets/v1/secrets.proto
3 | # plugin: python-betterproto
4 | # This file has been @generated
5 |
6 | from dataclasses import dataclass
7 | from typing import (
8 | TYPE_CHECKING,
9 | Dict,
10 | Optional,
11 | )
12 |
13 | import betterproto
14 | import grpclib
15 | from betterproto.grpc.grpclib_server import ServiceBase
16 |
17 |
18 | if TYPE_CHECKING:
19 | import grpclib.server
20 | from betterproto.grpc.grpclib_client import MetadataLike
21 | from grpclib.metadata import Deadline
22 |
23 |
24 | @dataclass(eq=False, repr=False)
25 | class SecretPutRequest(betterproto.Message):
26 | """Request to put a secret to a Secret Store"""
27 |
28 | secret: "Secret" = betterproto.message_field(1)
29 | """The Secret to put to the Secret store"""
30 |
31 | value: bytes = betterproto.bytes_field(2)
32 | """The value to assign to that secret"""
33 |
34 |
35 | @dataclass(eq=False, repr=False)
36 | class SecretPutResponse(betterproto.Message):
37 | """Result from putting the secret to a Secret Store"""
38 |
39 | secret_version: "SecretVersion" = betterproto.message_field(1)
40 | """The id of the secret"""
41 |
42 |
43 | @dataclass(eq=False, repr=False)
44 | class SecretAccessRequest(betterproto.Message):
45 | """Request to get a secret from a Secret Store"""
46 |
47 | secret_version: "SecretVersion" = betterproto.message_field(1)
48 | """The id of the secret"""
49 |
50 |
51 | @dataclass(eq=False, repr=False)
52 | class SecretAccessResponse(betterproto.Message):
53 | """The secret response"""
54 |
55 | secret_version: "SecretVersion" = betterproto.message_field(1)
56 | """The version of the secret that was requested"""
57 |
58 | value: bytes = betterproto.bytes_field(2)
59 | """The value of the secret"""
60 |
61 |
62 | @dataclass(eq=False, repr=False)
63 | class Secret(betterproto.Message):
64 | """The secret container"""
65 |
66 | name: str = betterproto.string_field(1)
67 | """The secret name"""
68 |
69 |
70 | @dataclass(eq=False, repr=False)
71 | class SecretVersion(betterproto.Message):
72 | """A version of a secret"""
73 |
74 | secret: "Secret" = betterproto.message_field(1)
75 | """Reference to the secret container"""
76 |
77 | version: str = betterproto.string_field(2)
78 | """The secret version"""
79 |
80 |
81 | class SecretManagerStub(betterproto.ServiceStub):
82 | async def put(
83 | self,
84 | secret_put_request: "SecretPutRequest",
85 | *,
86 | timeout: Optional[float] = None,
87 | deadline: Optional["Deadline"] = None,
88 | metadata: Optional["MetadataLike"] = None
89 | ) -> "SecretPutResponse":
90 | return await self._unary_unary(
91 | "/nitric.proto.secrets.v1.SecretManager/Put",
92 | secret_put_request,
93 | SecretPutResponse,
94 | timeout=timeout,
95 | deadline=deadline,
96 | metadata=metadata,
97 | )
98 |
99 | async def access(
100 | self,
101 | secret_access_request: "SecretAccessRequest",
102 | *,
103 | timeout: Optional[float] = None,
104 | deadline: Optional["Deadline"] = None,
105 | metadata: Optional["MetadataLike"] = None
106 | ) -> "SecretAccessResponse":
107 | return await self._unary_unary(
108 | "/nitric.proto.secrets.v1.SecretManager/Access",
109 | secret_access_request,
110 | SecretAccessResponse,
111 | timeout=timeout,
112 | deadline=deadline,
113 | metadata=metadata,
114 | )
115 |
116 |
117 | class SecretManagerBase(ServiceBase):
118 | async def put(self, secret_put_request: "SecretPutRequest") -> "SecretPutResponse":
119 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
120 |
121 | async def access(
122 | self, secret_access_request: "SecretAccessRequest"
123 | ) -> "SecretAccessResponse":
124 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
125 |
126 | async def __rpc_put(
127 | self, stream: "grpclib.server.Stream[SecretPutRequest, SecretPutResponse]"
128 | ) -> None:
129 | request = await stream.recv_message()
130 | response = await self.put(request)
131 | await stream.send_message(response)
132 |
133 | async def __rpc_access(
134 | self, stream: "grpclib.server.Stream[SecretAccessRequest, SecretAccessResponse]"
135 | ) -> None:
136 | request = await stream.recv_message()
137 | response = await self.access(request)
138 | await stream.send_message(response)
139 |
140 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
141 | return {
142 | "/nitric.proto.secrets.v1.SecretManager/Put": grpclib.const.Handler(
143 | self.__rpc_put,
144 | grpclib.const.Cardinality.UNARY_UNARY,
145 | SecretPutRequest,
146 | SecretPutResponse,
147 | ),
148 | "/nitric.proto.secrets.v1.SecretManager/Access": grpclib.const.Handler(
149 | self.__rpc_access,
150 | grpclib.const.Cardinality.UNARY_UNARY,
151 | SecretAccessRequest,
152 | SecretAccessResponse,
153 | ),
154 | }
155 |
--------------------------------------------------------------------------------
/nitric/proto/sql/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/sql/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/sql/v1/__init__.py:
--------------------------------------------------------------------------------
1 | # Generated by the protocol buffer compiler. DO NOT EDIT!
2 | # sources: nitric/proto/sql/v1/sql.proto
3 | # plugin: python-betterproto
4 | # This file has been @generated
5 |
6 | from dataclasses import dataclass
7 | from typing import (
8 | TYPE_CHECKING,
9 | Dict,
10 | Optional,
11 | )
12 |
13 | import betterproto
14 | import grpclib
15 | from betterproto.grpc.grpclib_server import ServiceBase
16 |
17 |
18 | if TYPE_CHECKING:
19 | import grpclib.server
20 | from betterproto.grpc.grpclib_client import MetadataLike
21 | from grpclib.metadata import Deadline
22 |
23 |
24 | @dataclass(eq=False, repr=False)
25 | class SqlConnectionStringRequest(betterproto.Message):
26 | database_name: str = betterproto.string_field(1)
27 | """The name of the database to retrieve the connection string for"""
28 |
29 |
30 | @dataclass(eq=False, repr=False)
31 | class SqlConnectionStringResponse(betterproto.Message):
32 | connection_string: str = betterproto.string_field(1)
33 | """The connection string for the database"""
34 |
35 |
36 | class SqlStub(betterproto.ServiceStub):
37 | async def connection_string(
38 | self,
39 | sql_connection_string_request: "SqlConnectionStringRequest",
40 | *,
41 | timeout: Optional[float] = None,
42 | deadline: Optional["Deadline"] = None,
43 | metadata: Optional["MetadataLike"] = None
44 | ) -> "SqlConnectionStringResponse":
45 | return await self._unary_unary(
46 | "/nitric.proto.sql.v1.Sql/ConnectionString",
47 | sql_connection_string_request,
48 | SqlConnectionStringResponse,
49 | timeout=timeout,
50 | deadline=deadline,
51 | metadata=metadata,
52 | )
53 |
54 |
55 | class SqlBase(ServiceBase):
56 | async def connection_string(
57 | self, sql_connection_string_request: "SqlConnectionStringRequest"
58 | ) -> "SqlConnectionStringResponse":
59 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
60 |
61 | async def __rpc_connection_string(
62 | self,
63 | stream: "grpclib.server.Stream[SqlConnectionStringRequest, SqlConnectionStringResponse]",
64 | ) -> None:
65 | request = await stream.recv_message()
66 | response = await self.connection_string(request)
67 | await stream.send_message(response)
68 |
69 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
70 | return {
71 | "/nitric.proto.sql.v1.Sql/ConnectionString": grpclib.const.Handler(
72 | self.__rpc_connection_string,
73 | grpclib.const.Cardinality.UNARY_UNARY,
74 | SqlConnectionStringRequest,
75 | SqlConnectionStringResponse,
76 | ),
77 | }
78 |
--------------------------------------------------------------------------------
/nitric/proto/storage/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/storage/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/topics/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/topics/__init__.py
--------------------------------------------------------------------------------
/nitric/proto/topics/v1/__init__.py:
--------------------------------------------------------------------------------
1 | # Generated by the protocol buffer compiler. DO NOT EDIT!
2 | # sources: nitric/proto/topics/v1/topics.proto
3 | # plugin: python-betterproto
4 | # This file has been @generated
5 |
6 | from dataclasses import dataclass
7 | from datetime import timedelta
8 | from typing import (
9 | TYPE_CHECKING,
10 | AsyncIterable,
11 | AsyncIterator,
12 | Dict,
13 | Iterable,
14 | Optional,
15 | Union,
16 | )
17 |
18 | import betterproto
19 | import betterproto.lib.google.protobuf as betterproto_lib_google_protobuf
20 | import grpclib
21 | from betterproto.grpc.grpclib_server import ServiceBase
22 |
23 |
24 | if TYPE_CHECKING:
25 | import grpclib.server
26 | from betterproto.grpc.grpclib_client import MetadataLike
27 | from grpclib.metadata import Deadline
28 |
29 |
30 | @dataclass(eq=False, repr=False)
31 | class ClientMessage(betterproto.Message):
32 | """
33 | ClientMessage is the message sent from the service to the nitric server
34 | """
35 |
36 | id: str = betterproto.string_field(1)
37 | """globally unique ID of the request/response pair"""
38 |
39 | registration_request: "RegistrationRequest" = betterproto.message_field(
40 | 2, group="content"
41 | )
42 | """Register a subscription to a topic"""
43 |
44 | message_response: "MessageResponse" = betterproto.message_field(3, group="content")
45 | """Handle a message received from a topic"""
46 |
47 |
48 | @dataclass(eq=False, repr=False)
49 | class MessageRequest(betterproto.Message):
50 | topic_name: str = betterproto.string_field(1)
51 | message: "TopicMessage" = betterproto.message_field(2)
52 | """Message Type"""
53 |
54 |
55 | @dataclass(eq=False, repr=False)
56 | class MessageResponse(betterproto.Message):
57 | success: bool = betterproto.bool_field(1)
58 |
59 |
60 | @dataclass(eq=False, repr=False)
61 | class ServerMessage(betterproto.Message):
62 | """
63 | ServerMessage is the message sent from the nitric server to the service
64 | """
65 |
66 | id: str = betterproto.string_field(1)
67 | """globally unique ID of the request/response pair"""
68 |
69 | registration_response: "RegistrationResponse" = betterproto.message_field(
70 | 2, group="content"
71 | )
72 | """Response to a topic subscription request"""
73 |
74 | message_request: "MessageRequest" = betterproto.message_field(3, group="content")
75 | """Response to a topic message request"""
76 |
77 |
78 | @dataclass(eq=False, repr=False)
79 | class RegistrationRequest(betterproto.Message):
80 | topic_name: str = betterproto.string_field(1)
81 |
82 |
83 | @dataclass(eq=False, repr=False)
84 | class RegistrationResponse(betterproto.Message):
85 | pass
86 |
87 |
88 | @dataclass(eq=False, repr=False)
89 | class TopicMessage(betterproto.Message):
90 | struct_payload: "betterproto_lib_google_protobuf.Struct" = (
91 | betterproto.message_field(1, group="content")
92 | )
93 |
94 |
95 | @dataclass(eq=False, repr=False)
96 | class TopicPublishRequest(betterproto.Message):
97 | """Request to publish a message to a topic"""
98 |
99 | topic_name: str = betterproto.string_field(1)
100 | """The name of the topic to publish the topic to"""
101 |
102 | message: "TopicMessage" = betterproto.message_field(2)
103 | """The message to be published"""
104 |
105 | delay: timedelta = betterproto.message_field(3)
106 | """An optional delay specified in seconds (minimum 10 seconds)"""
107 |
108 |
109 | @dataclass(eq=False, repr=False)
110 | class TopicPublishResponse(betterproto.Message):
111 | """Result of publishing an topic"""
112 |
113 | pass
114 |
115 |
116 | class TopicsStub(betterproto.ServiceStub):
117 | async def publish(
118 | self,
119 | topic_publish_request: "TopicPublishRequest",
120 | *,
121 | timeout: Optional[float] = None,
122 | deadline: Optional["Deadline"] = None,
123 | metadata: Optional["MetadataLike"] = None
124 | ) -> "TopicPublishResponse":
125 | return await self._unary_unary(
126 | "/nitric.proto.topics.v1.Topics/Publish",
127 | topic_publish_request,
128 | TopicPublishResponse,
129 | timeout=timeout,
130 | deadline=deadline,
131 | metadata=metadata,
132 | )
133 |
134 |
135 | class SubscriberStub(betterproto.ServiceStub):
136 | async def subscribe(
137 | self,
138 | client_message_iterator: Union[
139 | AsyncIterable["ClientMessage"], Iterable["ClientMessage"]
140 | ],
141 | *,
142 | timeout: Optional[float] = None,
143 | deadline: Optional["Deadline"] = None,
144 | metadata: Optional["MetadataLike"] = None
145 | ) -> AsyncIterator["ServerMessage"]:
146 | async for response in self._stream_stream(
147 | "/nitric.proto.topics.v1.Subscriber/Subscribe",
148 | client_message_iterator,
149 | ClientMessage,
150 | ServerMessage,
151 | timeout=timeout,
152 | deadline=deadline,
153 | metadata=metadata,
154 | ):
155 | yield response
156 |
157 |
158 | class TopicsBase(ServiceBase):
159 | async def publish(
160 | self, topic_publish_request: "TopicPublishRequest"
161 | ) -> "TopicPublishResponse":
162 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
163 |
164 | async def __rpc_publish(
165 | self, stream: "grpclib.server.Stream[TopicPublishRequest, TopicPublishResponse]"
166 | ) -> None:
167 | request = await stream.recv_message()
168 | response = await self.publish(request)
169 | await stream.send_message(response)
170 |
171 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
172 | return {
173 | "/nitric.proto.topics.v1.Topics/Publish": grpclib.const.Handler(
174 | self.__rpc_publish,
175 | grpclib.const.Cardinality.UNARY_UNARY,
176 | TopicPublishRequest,
177 | TopicPublishResponse,
178 | ),
179 | }
180 |
181 |
182 | class SubscriberBase(ServiceBase):
183 | async def subscribe(
184 | self, client_message_iterator: AsyncIterator["ClientMessage"]
185 | ) -> AsyncIterator["ServerMessage"]:
186 | raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
187 | yield ServerMessage()
188 |
189 | async def __rpc_subscribe(
190 | self, stream: "grpclib.server.Stream[ClientMessage, ServerMessage]"
191 | ) -> None:
192 | request = stream.__aiter__()
193 | await self._call_rpc_handler_server_stream(
194 | self.subscribe,
195 | stream,
196 | request,
197 | )
198 |
199 | def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
200 | return {
201 | "/nitric.proto.topics.v1.Subscriber/Subscribe": grpclib.const.Handler(
202 | self.__rpc_subscribe,
203 | grpclib.const.Cardinality.STREAM_STREAM,
204 | ClientMessage,
205 | ServerMessage,
206 | ),
207 | }
208 |
--------------------------------------------------------------------------------
/nitric/proto/websockets/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/proto/websockets/__init__.py
--------------------------------------------------------------------------------
/nitric/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nitrictech/python-sdk/f10852bc2505062893b31bafcbabd16c844e6cb4/nitric/py.typed
--------------------------------------------------------------------------------
/nitric/resources/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | """Nitric Python SDK API Documentation. See: https://nitric.io/docs?lang=python for full framework documentation."""
20 |
21 | from nitric.resources.apis import Api, api, ApiOptions, ApiDetails, JwtSecurityDefinition, oidc_rule
22 | from nitric.resources.buckets import Bucket, bucket, BucketNotificationContext, FileNotificationContext
23 | from nitric.resources.kv import KeyValueStoreRef, kv
24 | from nitric.resources.schedules import ScheduleServer, schedule
25 | from nitric.resources.secrets import Secret, secret
26 | from nitric.resources.topics import Topic, topic
27 | from nitric.resources.websockets import Websocket, websocket
28 | from nitric.resources.queues import Queue, queue
29 | from nitric.resources.sql import Sql, sql
30 | from nitric.resources.job import job, Job
31 |
32 | __all__ = [
33 | "api",
34 | "Api",
35 | "ApiOptions",
36 | "ApiDetails",
37 | "JwtSecurityDefinition",
38 | "bucket",
39 | "Bucket",
40 | "BucketNotificationContext",
41 | "FileNotificationContext",
42 | "kv",
43 | "KeyValueStoreRef",
44 | "job",
45 | "Job",
46 | "oidc_rule",
47 | "queue",
48 | "Queue",
49 | "ScheduleServer",
50 | "schedule",
51 | "secret",
52 | "Secret",
53 | "sql",
54 | "Sql",
55 | "topic",
56 | "Topic",
57 | "websocket",
58 | "Websocket",
59 | ]
60 |
--------------------------------------------------------------------------------
/nitric/resources/job.py:
--------------------------------------------------------------------------------
1 | from nitric.resources.resource import SecureResource
2 | from nitric.application import Nitric
3 | from nitric.proto.resources.v1 import (
4 | Action,
5 | JobResource,
6 | ResourceDeclareRequest,
7 | ResourceIdentifier,
8 | ResourceType,
9 | )
10 | from nitric.context import JobContext
11 | import logging
12 | import betterproto
13 | from nitric.proto.batch.v1 import (
14 | BatchStub,
15 | JobSubmitRequest,
16 | JobData,
17 | JobStub,
18 | RegistrationRequest,
19 | ClientMessage,
20 | JobResponse as ProtoJobResponse,
21 | JobResourceRequirements,
22 | )
23 | from nitric.exception import exception_from_grpc_error
24 | from grpclib import GRPCError
25 | from grpclib.client import Channel
26 | from typing import Callable, Any, Optional, Literal, List
27 | from nitric.context import FunctionServer, Handler
28 | from nitric.channel import ChannelManager
29 | from nitric.bidi import AsyncNotifierList
30 | from nitric.utils import struct_from_dict
31 | import grpclib
32 |
33 |
34 | JobPermission = Literal["submit"]
35 | JobHandle = Handler[JobContext]
36 |
37 |
38 | class JobHandler(FunctionServer):
39 | """Function worker for Jobs."""
40 |
41 | _handler: JobHandle
42 | _registration_request: RegistrationRequest
43 | _responses: AsyncNotifierList[ClientMessage]
44 |
45 | def __init__(
46 | self,
47 | job_name: str,
48 | handler: JobHandle,
49 | cpus: float | None = None,
50 | memory: int | None = None,
51 | gpus: int | None = None,
52 | ):
53 | """Construct a new JobHandler."""
54 | self._handler = handler
55 | self._responses = AsyncNotifierList()
56 | self._registration_request = RegistrationRequest(
57 | job_name=job_name,
58 | requirements=JobResourceRequirements(
59 | cpus=cpus if cpus is not None else 0,
60 | memory=memory if memory is not None else 0,
61 | gpus=gpus if gpus is not None else 0,
62 | ),
63 | )
64 |
65 | async def _message_request_iterator(self):
66 | # Register with the server
67 | yield ClientMessage(registration_request=self._registration_request)
68 | # wait for any responses for the server and send them
69 | async for response in self._responses:
70 | yield response
71 |
72 | async def start(self) -> None:
73 | """Register this job handler and listen for tasks."""
74 | channel = ChannelManager.get_channel()
75 | server = JobStub(channel=channel)
76 |
77 | try:
78 | async for server_msg in server.handle_job(self._message_request_iterator()):
79 | msg_type, _ = betterproto.which_one_of(server_msg, "content")
80 |
81 | if msg_type == "registration_response":
82 | continue
83 | if msg_type == "job_request":
84 | ctx = JobContext._from_request(server_msg)
85 |
86 | response: ClientMessage
87 | try:
88 | resp_ctx = await self._handler(ctx)
89 | if resp_ctx is None:
90 | resp_ctx = ctx
91 |
92 | response = ClientMessage(
93 | id=server_msg.id,
94 | job_response=ProtoJobResponse(success=ctx.res.success),
95 | )
96 | except Exception as e: # pylint: disable=broad-except
97 | logging.exception("An unhandled error occurred in a job event handler: %s", e)
98 | response = ClientMessage(id=server_msg.id, job_response=ProtoJobResponse(success=False))
99 | await self._responses.add_item(response)
100 | except grpclib.exceptions.GRPCError as e:
101 | print(f"Stream terminated: {e.message}")
102 | except grpclib.exceptions.StreamTerminatedError:
103 | print("Stream from membrane closed.")
104 | finally:
105 | print("Closing client stream")
106 | channel.close()
107 |
108 |
109 | class JobRef:
110 | """A reference to a deployed job, used to interact with the job at runtime."""
111 |
112 | _channel: Channel
113 | _stub: BatchStub
114 | name: str
115 |
116 | def __init__(self, name: str) -> None:
117 | """Construct a reference to a deployed Job."""
118 | self._channel: Channel = ChannelManager.get_channel()
119 | self._stub = BatchStub(channel=self._channel)
120 | self.name = name
121 |
122 | def __del__(self) -> None:
123 | # close the channel when this client is destroyed
124 | if self._channel is not None:
125 | self._channel.close()
126 |
127 | async def submit(self, data: dict[str, Any]) -> None:
128 | """Submit a new execution for this job definition."""
129 | await self._stub.submit_job(
130 | job_submit_request=JobSubmitRequest(job_name=self.name, data=JobData(struct=struct_from_dict(data)))
131 | )
132 |
133 |
134 | class Job(SecureResource):
135 | """A Job Definition."""
136 |
137 | name: str
138 |
139 | def __init__(self, name: str):
140 | """Job definition constructor."""
141 | super().__init__(name)
142 | self.name = name
143 |
144 | async def _register(self) -> None:
145 | try:
146 | await self._resources_stub.declare(
147 | resource_declare_request=ResourceDeclareRequest(
148 | id=_to_resource_identifier(self),
149 | job=JobResource(),
150 | )
151 | )
152 |
153 | except GRPCError as grpc_err:
154 | raise exception_from_grpc_error(grpc_err) from grpc_err
155 |
156 | def _perms_to_actions(self, *args: JobPermission) -> List[Action]:
157 | _permMap: dict[JobPermission, List[Action]] = {"submit": [Action.JobSubmit]}
158 |
159 | return [action for perm in args for action in _permMap[perm]]
160 |
161 | def allow(self, perm: JobPermission, *args: JobPermission) -> JobRef:
162 | """Request the specified permissions to this resource."""
163 | str_args = [perm] + [str(permission) for permission in args]
164 | self._register_policy(*str_args)
165 |
166 | return JobRef(self.name)
167 |
168 | def _to_resource_id(self) -> ResourceIdentifier:
169 | return ResourceIdentifier(name=self.name, type=ResourceType.Job)
170 |
171 | def __call__(
172 | self, cpus: Optional[float] = None, memory: Optional[int] = None, gpus: Optional[int] = None
173 | ) -> Callable[[JobHandle], None]:
174 | """Define the handler for this job definition."""
175 |
176 | def decorator(function: JobHandle) -> None:
177 | wrkr = JobHandler(self.name, function, cpus, memory, gpus)
178 | Nitric._register_worker(wrkr)
179 |
180 | return decorator
181 |
182 |
183 | def _to_resource_identifier(b: Job) -> ResourceIdentifier:
184 | return ResourceIdentifier(name=b.name, type=ResourceType.Job)
185 |
186 |
187 | def job(name: str) -> Job:
188 | """
189 | Create and register a job.
190 |
191 | If a job has already been registered with the same name, the original reference will be reused.
192 | """
193 | # type ignored because the create call are treated as protected.
194 | return Nitric._create_resource(Job, name) # type: ignore pylint: disable=protected-access
195 |
--------------------------------------------------------------------------------
/nitric/resources/kv.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from __future__ import annotations
20 |
21 | from typing import Any, List, Literal, AsyncIterator, Optional
22 |
23 | from grpclib import GRPCError
24 | from grpclib.client import Channel
25 |
26 | from nitric.application import Nitric
27 | from nitric.exception import exception_from_grpc_error
28 | from nitric.proto.kvstore.v1 import (
29 | Store,
30 | KvStoreScanKeysRequest,
31 | KvStoreDeleteKeyRequest,
32 | KvStoreGetValueRequest,
33 | KvStoreSetValueRequest,
34 | KvStoreStub,
35 | ValueRef,
36 | )
37 | from nitric.proto.resources.v1 import (
38 | Action,
39 | KeyValueStoreResource,
40 | ResourceDeclareRequest,
41 | ResourceIdentifier,
42 | ResourceType,
43 | )
44 | from nitric.resources.resource import SecureResource
45 | from nitric.utils import dict_from_struct, struct_from_dict
46 | from nitric.channel import ChannelManager
47 |
48 |
49 | class KeyValueStoreRef:
50 | """A reference to a deployed key value store, used to interact with the key value store at runtime."""
51 |
52 | _kv_stub: KvStoreStub
53 | _channel: Channel
54 | name: str
55 |
56 | def __init__(self, name: str):
57 | """Construct a reference to a deployed key value store."""
58 | self._channel: Channel = ChannelManager.get_channel()
59 | self._kv_stub = KvStoreStub(channel=self._channel)
60 | self.name = name
61 |
62 | def __del__(self):
63 | # close the channel when this client is destroyed
64 | if self._channel is not None:
65 | self._channel.close()
66 |
67 | async def set(self, key: str, value: dict[str, Any]) -> None:
68 | """Set a key and value in the key value store."""
69 | ref = ValueRef(store=self.name, key=key)
70 |
71 | req = KvStoreSetValueRequest(ref=ref, content=struct_from_dict(value))
72 |
73 | try:
74 | await self._kv_stub.set_value(kv_store_set_value_request=req)
75 | except GRPCError as grpc_err:
76 | raise exception_from_grpc_error(grpc_err) from grpc_err
77 |
78 | async def get(self, key: str) -> dict[str, Any]:
79 | """Return a value from the key value store."""
80 | ref = ValueRef(store=self.name, key=key)
81 |
82 | req = KvStoreGetValueRequest(ref=ref)
83 |
84 | try:
85 | resp = await self._kv_stub.get_value(kv_store_get_value_request=req)
86 |
87 | return dict_from_struct(resp.value.content)
88 | except GRPCError as grpc_err:
89 | raise exception_from_grpc_error(grpc_err) from grpc_err
90 |
91 | async def keys(self, prefix: Optional[str] = "") -> AsyncIterator[str]:
92 | """Return a list of keys from the key value store."""
93 | if prefix is None:
94 | prefix = ""
95 |
96 | req = KvStoreScanKeysRequest(
97 | store=Store(name=self.name),
98 | prefix=prefix,
99 | )
100 |
101 | try:
102 | response_iterator = self._kv_stub.scan_keys(kv_store_scan_keys_request=req)
103 | async for item in response_iterator:
104 | yield item.key
105 | except GRPCError as grpc_err:
106 | raise exception_from_grpc_error(grpc_err) from grpc_err
107 |
108 | return
109 |
110 | async def delete(self, key: str) -> None:
111 | """Delete a key from the key value store."""
112 | ref = ValueRef(store=self.name, key=key)
113 |
114 | req = KvStoreDeleteKeyRequest(ref=ref)
115 |
116 | try:
117 | await self._kv_stub.delete_key(kv_store_delete_key_request=req)
118 | except GRPCError as grpc_err:
119 | raise exception_from_grpc_error(grpc_err) from grpc_err
120 |
121 |
122 | KVPermission = Literal["get", "set", "delete"]
123 |
124 |
125 | class KeyValueStore(SecureResource):
126 | """A key value store resource."""
127 |
128 | async def _register(self) -> None:
129 | try:
130 | await self._resources_stub.declare(
131 | resource_declare_request=ResourceDeclareRequest(
132 | id=self._to_resource_id(), key_value_store=KeyValueStoreResource()
133 | )
134 | )
135 | except GRPCError as grpc_err:
136 | raise exception_from_grpc_error(grpc_err) from grpc_err
137 |
138 | def _to_resource_id(self) -> ResourceIdentifier:
139 | return ResourceIdentifier(name=self.name, type=ResourceType.KeyValueStore)
140 |
141 | def _perms_to_actions(self, *args: KVPermission) -> List[Action]:
142 | permission_actions_map: dict[KVPermission, List[Action]] = {
143 | "get": [Action.KeyValueStoreRead],
144 | "set": [Action.KeyValueStoreWrite],
145 | "delete": [Action.KeyValueStoreDelete],
146 | }
147 |
148 | return [action for perm in args for action in permission_actions_map[perm]]
149 |
150 | def allow(self, perm: KVPermission, *args: KVPermission) -> KeyValueStoreRef:
151 | """Request the required permissions for this collection."""
152 | # Ensure registration of the resource is complete before requesting permissions.
153 | str_args = [str(perm)] + [str(permission) for permission in args]
154 | self._register_policy(*str_args)
155 |
156 | return KeyValueStoreRef(self.name)
157 |
158 |
159 | def kv(name: str) -> KeyValueStore:
160 | """
161 | Create and register a key value store.
162 |
163 | If a key value store has already been registered with the same name, the original reference will be reused.
164 | """
165 | return Nitric._create_resource(KeyValueStore, name) # type: ignore pylint: disable=protected-access
166 |
--------------------------------------------------------------------------------
/nitric/resources/resource.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from __future__ import annotations
20 |
21 | import asyncio
22 | from abc import ABC, abstractmethod
23 | from asyncio import Task
24 | from typing import Any, List, Optional, Sequence, Type, TypeVar
25 |
26 | from grpclib import GRPCError
27 |
28 | from nitric.exception import NitricResourceException, exception_from_grpc_error
29 | from nitric.proto.resources.v1 import (
30 | Action,
31 | PolicyResource,
32 | ResourceDeclareRequest,
33 | ResourceIdentifier,
34 | ResourcesStub,
35 | ResourceType,
36 | )
37 | from nitric.channel import ChannelManager
38 |
39 | T = TypeVar("T", bound="Resource")
40 |
41 |
42 | class Resource(ABC):
43 | """A base resource class with common functionality."""
44 |
45 | name: str
46 |
47 | def __init__(self, name: str):
48 | """Construct a new resource."""
49 | self.name = name
50 | self._reg: Optional[Task[Any]] = None # type: ignore
51 | self._channel = ChannelManager.get_channel()
52 | self._resources_stub = ResourcesStub(channel=self._channel)
53 |
54 | @abstractmethod
55 | async def _register(self) -> None:
56 | pass
57 |
58 | @classmethod
59 | def make(cls: Type[T], name: str, *args: Sequence[Any], **kwargs: dict[str, Any]) -> T:
60 | """
61 | Create and register the resource.
62 |
63 | The registration process for resources async, so this method should be used instead of __init__.
64 | """
65 | r = cls(name, *args, **kwargs) # type: ignore
66 | try:
67 | loop = asyncio.get_running_loop()
68 | r._reg = loop.create_task(r._register())
69 | except RuntimeError:
70 | loop = asyncio.get_event_loop()
71 | loop.run_until_complete(r._register())
72 |
73 | return r
74 |
75 |
76 | class SecureResource(Resource):
77 | """A secure base resource class."""
78 |
79 | @abstractmethod
80 | def _to_resource_id(self) -> ResourceIdentifier:
81 | pass
82 |
83 | @abstractmethod
84 | def _perms_to_actions(self, *args: Any) -> List[Action]:
85 | pass
86 |
87 | async def _register_policy_async(self, *args: str) -> None:
88 | policy = PolicyResource(
89 | principals=[ResourceIdentifier(type=ResourceType.Service)],
90 | actions=self._perms_to_actions(*args),
91 | resources=[self._to_resource_id()],
92 | )
93 | try:
94 | await self._resources_stub.declare(
95 | resource_declare_request=ResourceDeclareRequest(
96 | id=ResourceIdentifier(type=ResourceType.Policy), policy=policy
97 | )
98 | )
99 | except GRPCError as grpc_err:
100 | raise exception_from_grpc_error(grpc_err) from grpc_err
101 |
102 | def _register_policy(self, *args: str) -> None:
103 | try:
104 | loop = asyncio.get_event_loop()
105 | loop.run_until_complete(self._register_policy_async(*args))
106 | except RuntimeError:
107 | raise NitricResourceException(
108 | "Nitric resources cannot be declared at runtime e.g. within the scope of a runtime function. \
109 | Move resource declarations to the top level of scripts so that they can be safely provisioned"
110 | ) from None
111 |
--------------------------------------------------------------------------------
/nitric/resources/schedules.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from __future__ import annotations
20 |
21 | import logging
22 | from datetime import timedelta
23 | from enum import Enum
24 | from typing import Callable, List
25 |
26 | import betterproto
27 | import grpclib.exceptions
28 |
29 | from nitric.application import Nitric
30 | from nitric.bidi import AsyncNotifierList
31 | from nitric.context import FunctionServer, IntervalContext, IntervalHandler
32 | from nitric.proto.schedules.v1 import (
33 | ClientMessage,
34 | IntervalResponse,
35 | RegistrationRequest,
36 | ScheduleCron,
37 | ScheduleEvery,
38 | SchedulesStub,
39 | )
40 | from nitric.channel import ChannelManager
41 |
42 |
43 | class ScheduleServer(FunctionServer):
44 | """A schedule for running functions on a cadence."""
45 |
46 | description: str
47 |
48 | handler: IntervalHandler
49 | _registration_request: RegistrationRequest
50 | _responses: AsyncNotifierList[ClientMessage]
51 |
52 | def __init__(self, description: str):
53 | """Create a schedule for running functions on a cadence."""
54 | self.description = description
55 | self._responses = AsyncNotifierList()
56 |
57 | def every(self, rate_description: str, handler: IntervalHandler) -> None:
58 | """
59 | Register a function to be run at the specified rate.
60 |
61 | E.g. every("3 hours")
62 | """
63 | self._registration_request = RegistrationRequest(
64 | schedule_name=self.description,
65 | every=ScheduleEvery(rate=rate_description.lower()),
66 | )
67 |
68 | self.handler = handler
69 |
70 | Nitric._register_worker(self) # type: ignore pylint: disable=protected-access
71 |
72 | def cron(self, cron_expression: str, handler: IntervalHandler) -> None:
73 | """
74 | Register a function to be run at the specified cron schedule.
75 |
76 | E.g. cron("3 * * * *")
77 | """
78 | self._registration_request = RegistrationRequest(
79 | schedule_name=self.description,
80 | cron=ScheduleCron(expression=cron_expression),
81 | )
82 |
83 | self.handler = handler
84 |
85 | Nitric._register_worker(self) # type: ignore pylint: disable=protected-access
86 |
87 | async def _schedule_request_iterator(self):
88 | # Register with the server
89 | yield ClientMessage(registration_request=self._registration_request)
90 | # wait for any responses for the server and send them
91 | async for response in self._responses:
92 | yield response
93 |
94 | async def start(self) -> None:
95 | """Register this schedule and start listening for requests."""
96 | channel = ChannelManager.get_channel()
97 | schedules_stub = SchedulesStub(channel=channel)
98 |
99 | try:
100 | async for server_msg in schedules_stub.schedule(self._schedule_request_iterator()):
101 | msg_type, _ = betterproto.which_one_of(server_msg, "content")
102 |
103 | if msg_type == "registration_response":
104 | continue
105 | if msg_type == "interval_request":
106 | ctx = IntervalContext(server_msg)
107 | try:
108 | await self.handler(ctx)
109 | except Exception as e: # pylint: disable=broad-except
110 | logging.exception("An unhandled error occurred in a scheduled function: %s", e)
111 | resp = IntervalResponse()
112 | await self._responses.add_item(ClientMessage(id=server_msg.id, interval_response=resp))
113 | except grpclib.exceptions.GRPCError as e:
114 | print(f"Stream terminated: {e.message}")
115 | except grpclib.exceptions.StreamTerminatedError:
116 | print("Stream from membrane closed.")
117 | finally:
118 | print("Closing client stream")
119 | channel.close()
120 |
121 |
122 | class Frequency(Enum):
123 | """Valid schedule frequencies."""
124 |
125 | MINUTES = "minutes"
126 | HOURS = "hours"
127 | DAYS = "days"
128 |
129 | @staticmethod
130 | def from_str(value: str) -> Frequency:
131 | """Convert a string frequency value to Frequency."""
132 | try:
133 | return Frequency[value.strip().upper()]
134 | except Exception:
135 | raise ValueError(f"{value} is not a valid frequency")
136 |
137 | @staticmethod
138 | def as_str_list() -> List[str]:
139 | """Return all frequency values as a list of strings."""
140 | return [str(frequency.value) for frequency in Frequency]
141 |
142 | def as_time(self, rate: int) -> timedelta:
143 | """Convert the rate to minutes based on the frequency."""
144 | if self == Frequency.MINUTES:
145 | return timedelta(minutes=rate)
146 | elif self == Frequency.HOURS:
147 | return timedelta(hours=rate)
148 | elif self == Frequency.DAYS:
149 | return timedelta(days=rate)
150 | else:
151 | raise ValueError(f"{self} is not a valid frequency")
152 |
153 |
154 | class Schedule:
155 | """A raw schedule to be deployed and assigned a rate or cron interval."""
156 |
157 | def __init__(self, description: str):
158 | """Create a new schedule resource."""
159 | self.description = description
160 |
161 | def every(self, every: str) -> Callable[[IntervalHandler], ScheduleServer]:
162 | """
163 | Set the schedule interval.
164 |
165 | e.g. every('3 days').
166 | """
167 |
168 | def decorator(func: IntervalHandler) -> ScheduleServer:
169 | r = ScheduleServer(self.description)
170 | r.every(every, func)
171 | return r
172 |
173 | return decorator
174 |
175 | def cron(self, cron: str) -> Callable[[IntervalHandler], ScheduleServer]:
176 | """
177 | Set the schedule interval.
178 |
179 | e.g. cron('3 * * * *').
180 | """
181 |
182 | def decorator(func: IntervalHandler) -> ScheduleServer:
183 | r = ScheduleServer(self.description)
184 | r.cron(cron, func)
185 | return r
186 |
187 | return decorator
188 |
189 |
190 | def schedule(description: str) -> Schedule:
191 | """Return a schedule."""
192 | return Schedule(description=description)
193 |
--------------------------------------------------------------------------------
/nitric/resources/sql.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from __future__ import annotations
20 |
21 | from typing import Union
22 |
23 | from grpclib import GRPCError
24 | from grpclib.client import Channel
25 |
26 | from nitric.exception import exception_from_grpc_error
27 | from nitric.proto.resources.v1 import (
28 | SqlDatabaseResource,
29 | SqlDatabaseMigrations,
30 | ResourceDeclareRequest,
31 | ResourceIdentifier,
32 | ResourceType,
33 | )
34 | from nitric.resources.resource import Resource as BaseResource
35 | from nitric.channel import ChannelManager
36 | from nitric.application import Nitric
37 |
38 | from nitric.proto.sql.v1 import SqlStub, SqlConnectionStringRequest
39 |
40 |
41 | class Sql(BaseResource):
42 | """A SQL Database."""
43 |
44 | _channel: Channel
45 | _sql_stub: SqlStub
46 | name: str
47 | migrations: Union[str, None]
48 |
49 | def __init__(self, name: str, migrations: Union[str, None] = None):
50 | """Construct a new SQL Database."""
51 | super().__init__(name)
52 |
53 | self._channel: Union[Channel, None] = ChannelManager.get_channel()
54 | self._sql_stub = SqlStub(channel=self._channel)
55 | self.name = name
56 | self.migrations = migrations
57 |
58 | async def _register(self) -> None:
59 | try:
60 | await self._resources_stub.declare(
61 | resource_declare_request=ResourceDeclareRequest(
62 | id=ResourceIdentifier(name=self.name, type=ResourceType.SqlDatabase),
63 | sql_database=SqlDatabaseResource(
64 | migrations=SqlDatabaseMigrations(migrations_path=self.migrations if self.migrations else "")
65 | ),
66 | ),
67 | )
68 | except GRPCError as grpc_err:
69 | raise exception_from_grpc_error(grpc_err) from grpc_err
70 |
71 | async def connection_string(self) -> str:
72 | """Return the connection string for this SQL Database."""
73 | response = await self._sql_stub.connection_string(SqlConnectionStringRequest(database_name=self.name))
74 |
75 | return response.connection_string
76 |
77 |
78 | def sql(name: str, migrations: Union[str, None] = None) -> Sql:
79 | """
80 | Create and register a sql database.
81 |
82 | If a sql databse has already been registered with the same name, the original reference will be reused.
83 | """
84 | return Nitric._create_resource(Sql, name, migrations)
85 |
--------------------------------------------------------------------------------
/nitric/utils.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from typing import Any, Optional
20 |
21 | from betterproto.lib.google.protobuf import Struct
22 | from google.protobuf.json_format import MessageToDict
23 | from google.protobuf.struct_pb2 import Struct as WorkingStruct
24 |
25 |
26 | # These functions convert to/from python dict <-> betterproto.lib.google.protobuf.Struct
27 | # the existing Struct().from_dict() method doesn't work for Structs,
28 | # it relies on Message meta information, that isn't available for dynamic structs.
29 | def dict_from_struct(struct: Optional[Struct]) -> dict[Any, Any]:
30 | """Construct a dict from a Struct."""
31 | # Convert the bytes representation of the betterproto Struct into a protobuf Struct
32 | # in order to use the MessageToDict function to safely create a dict.
33 | if struct is None:
34 | return {}
35 | gpb_struct = WorkingStruct()
36 | gpb_struct.ParseFromString(bytes(struct))
37 | return MessageToDict(gpb_struct)
38 |
39 |
40 | def struct_from_dict(dictionary: Optional[dict[Any, Any]]) -> Struct:
41 | """Construct a Struct from a dict."""
42 | # Convert to dict into a Struct class from the protobuf library
43 | # since protobuf Structs are able to be created from a dict
44 | # unlike the Struct class from betterproto.
45 | if dictionary is None:
46 | return Struct()
47 | gpb_struct = WorkingStruct()
48 | gpb_struct.update(dictionary)
49 | # Convert the bytes representation of the protobuf Struct into the betterproto Struct
50 | # so that the returned Struct is compatible with other betterproto generated classes
51 | struct = Struct().parse(gpb_struct.SerializeToString())
52 | return struct
53 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools >= 40.9.0", "wheel"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [tool.black]
6 | line-length = 120
7 | include = '\.pyi?$'
8 | exclude = '''
9 | /(
10 | \.git
11 | | \.pytest_cache
12 | | \.tox
13 | | \.coverage
14 | | build
15 | | contracts
16 | | dist
17 | )/
18 | '''
19 |
20 | [tool.pylint]
21 | max-line-length = 120
22 | disable = ["protected-access"]
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 | import re
3 | from subprocess import Popen, PIPE
4 |
5 |
6 | def get_current_version_tag():
7 | process = Popen(["git", "describe", "--tags", "--match", "v[0-9]*"], stdout=PIPE)
8 | (output, err) = process.communicate()
9 | process.wait()
10 |
11 | tags = str(output, "utf-8").strip().split("\n")
12 |
13 | version_tags = [tag for tag in tags if re.match(r"^v?(\d*\.){2}\d$", tag)]
14 | rc_tags = [tag for tag in tags if re.match(r"^v?(\d*\.){2}\d*-rc\.\d*$", tag)]
15 |
16 | if len(version_tags) == 1:
17 | return version_tags.pop()[1:]
18 | elif len(rc_tags) == 1:
19 | base_tag, num_commits = rc_tags.pop().split("-rc.")[:2]
20 | return "{0}.dev{1}".format(base_tag, num_commits)[1:]
21 | else:
22 | return "0.0.0.dev0"
23 |
24 |
25 | with open("README.md", "r") as readme_file:
26 | long_description = readme_file.read()
27 |
28 | setuptools.setup(
29 | name="nitric",
30 | version=get_current_version_tag(),
31 | author="Nitric",
32 | author_email="team@nitric.io",
33 | description="The Nitric SDK for Python 3",
34 | long_description=long_description,
35 | long_description_content_type="text/markdown",
36 | url="https://github.com/nitrictech/python-sdk",
37 | packages=setuptools.find_packages(exclude=["tests", "tests.*"]),
38 | package_data={"nitric": ["py.typed"]},
39 | license_files=("LICENSE.txt",),
40 | classifiers=[
41 | "Programming Language :: Python :: 3",
42 | "Operating System :: OS Independent",
43 | ],
44 | setup_requires=["wheel"],
45 | install_requires=[
46 | "asyncio",
47 | "protobuf==4.23.3",
48 | "betterproto==2.0.0b6",
49 | "opentelemetry-api",
50 | "opentelemetry-sdk",
51 | "opentelemetry-exporter-otlp-proto-grpc",
52 | "opentelemetry-instrumentation-grpc",
53 | ],
54 | extras_require={
55 | "dev": [
56 | "tox==3.20.1",
57 | "twine==3.2.0",
58 | "pytest==7.3.2",
59 | "pytest-cov==4.1.0",
60 | "pre-commit==2.12.0",
61 | "black==22.3",
62 | "flake8==3.9.1",
63 | "flake8",
64 | "flake8-bugbear",
65 | "flake8-comprehensions",
66 | "flake8-string-format",
67 | "pydocstyle==6.0.0",
68 | "pip-licenses==3.3.1",
69 | "licenseheaders==0.8.8",
70 | "pdoc3==0.9.2",
71 | "markupsafe==2.0.1",
72 | "betterproto[compiler]==2.0.0b6",
73 | # "grpcio==1.33.2",
74 | "grpcio-tools==1.62.0",
75 | "twine==3.2.0",
76 | "mypy==1.3.0",
77 | ]
78 | },
79 | python_requires=">=3.11",
80 | )
81 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
--------------------------------------------------------------------------------
/tests/resources/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
--------------------------------------------------------------------------------
/tests/resources/test_queues.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from unittest import IsolatedAsyncioTestCase
20 | from unittest.mock import patch, AsyncMock
21 | from nitric.resources import queue
22 |
23 | from nitric.proto.resources.v1 import Action, ResourceDeclareRequest, ResourceIdentifier, ResourceType, PolicyResource
24 |
25 | # pylint: disable=protected-access,missing-function-docstring,missing-class-docstring
26 |
27 |
28 | class Object(object):
29 | pass
30 |
31 |
32 | class QueueTest(IsolatedAsyncioTestCase):
33 | def test_create_allow_sending(self):
34 | mock_declare = AsyncMock()
35 | mock_response = Object()
36 | mock_declare.return_value = mock_response
37 |
38 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
39 | queue("test-queue").allow("enqueue")
40 |
41 | # Check expected values were passed to Stub
42 | mock_declare.assert_called_with(
43 | resource_declare_request=ResourceDeclareRequest(
44 | id=ResourceIdentifier(type=ResourceType.Policy),
45 | policy=PolicyResource(
46 | principals=[ResourceIdentifier(type=ResourceType.Service)],
47 | actions=[
48 | Action.QueueEnqueue,
49 | ],
50 | resources=[ResourceIdentifier(type=ResourceType.Queue, name="test-queue")],
51 | ),
52 | )
53 | )
54 |
55 | def test_create_allow_receiving(self):
56 | mock_declare = AsyncMock()
57 | mock_response = Object()
58 | mock_declare.return_value = mock_response
59 |
60 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
61 | queue("test-queue").allow("dequeue")
62 |
63 | # Check expected values were passed to Stub
64 | mock_declare.assert_called_with(
65 | resource_declare_request=ResourceDeclareRequest(
66 | id=ResourceIdentifier(type=ResourceType.Policy),
67 | policy=PolicyResource(
68 | principals=[ResourceIdentifier(type=ResourceType.Service)],
69 | actions=[
70 | Action.QueueDequeue,
71 | ],
72 | resources=[ResourceIdentifier(type=ResourceType.Queue, name="test-queue")],
73 | ),
74 | )
75 | )
76 |
77 | def test_create_allow_all(self):
78 | mock_declare = AsyncMock()
79 | mock_response = Object()
80 | mock_declare.return_value = mock_response
81 |
82 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
83 | queue("test-queue").allow("enqueue", "dequeue")
84 |
85 | # Check expected values were passed to Stub
86 | mock_declare.assert_called_with(
87 | resource_declare_request=ResourceDeclareRequest(
88 | id=ResourceIdentifier(type=ResourceType.Policy),
89 | policy=PolicyResource(
90 | principals=[ResourceIdentifier(type=ResourceType.Service)],
91 | actions=[
92 | Action.QueueEnqueue,
93 | Action.QueueDequeue,
94 | ],
95 | resources=[ResourceIdentifier(type=ResourceType.Queue, name="test-queue")],
96 | ),
97 | )
98 | )
99 |
--------------------------------------------------------------------------------
/tests/resources/test_schedules.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from unittest import IsolatedAsyncioTestCase
20 |
21 | from nitric.resources import schedule, ScheduleServer
22 |
23 |
24 | # pylint: disable=protected-access,missing-function-docstring,missing-class-docstring
25 |
26 |
27 | class Object(object):
28 | pass
29 |
30 |
31 | class ApiTest(IsolatedAsyncioTestCase):
32 | def test_create_schedule(self):
33 | test_schedule = ScheduleServer("test-schedule")
34 |
35 | assert test_schedule is not None
36 | assert test_schedule.description == "test-schedule"
37 |
38 | def test_create_schedule_decorator_every(self):
39 | # test_schedule = schedule("test-schedule", "3 hours")(lambda ctx: ctx)
40 | test_schedule = schedule("test-schedule-every")
41 | schedule_server = test_schedule.every("3 hours")(lambda ctx: ctx)
42 |
43 | assert test_schedule is not None
44 | assert test_schedule.description == "test-schedule-every"
45 | assert (
46 | schedule_server._registration_request.schedule_name == "test-schedule-every"
47 | ) # pylint: disable=protected-access
48 | assert schedule_server._registration_request.every.rate == "3 hours" # pylint: disable=protected-access
49 |
50 | def test_create_schedule_decorator_cron(self):
51 | # test_schedule = schedule("test-schedule", "3 hours")(lambda ctx: ctx)
52 | test_schedule = schedule("test-schedule-cron")
53 | schedule_server = test_schedule.cron("* * * * *")(lambda ctx: ctx)
54 |
55 | assert test_schedule is not None
56 | assert test_schedule.description == "test-schedule-cron"
57 | assert (
58 | schedule_server._registration_request.schedule_name == "test-schedule-cron"
59 | ) # pylint: disable=protected-access
60 | assert schedule_server._registration_request.cron.expression == "* * * * *" # pylint: disable=protected-access
61 |
62 | # TODO: Re-implement schedule validation
63 | # def test_every_with_invalid_rate_description_frequency(self):
64 | # test_schedule = Schedule("test-schedule")
65 |
66 | # try:
67 | # test_schedule.every("3 months", lambda ctx: ctx)
68 | # pytest.fail()
69 | # except Exception as e:
70 | # assert str(e).startswith("invalid rate expression, frequency") is True
71 |
72 | # TODO: Re-implement schedule validation
73 | # def test_every_with_missing_rate_description_frequency(self):
74 | # test_schedule = Schedule("test-schedule")
75 |
76 | # try:
77 | # test_schedule.every("3", lambda ctx: ctx)
78 | # pytest.fail()
79 | # except Exception as e:
80 | # assert str(e).startswith("invalid rate expression, frequency") is True
81 |
82 | # TODO: Re-implement schedule validation
83 | # def test_every_with_invalid_rate_description_rate(self):
84 | # test_schedule = Schedule("test-schedule")
85 |
86 | # try:
87 | # test_schedule.every("three days", lambda ctx: ctx)
88 | # pytest.fail()
89 | # except Exception as e:
90 | # assert str(e).startswith("invalid rate expression, expression") is True
91 |
92 | # TODO: Re-implement schedule validation
93 | # def test_every_with_invalid_rate_description_frequency_and_rate(self):
94 | # test_schedule = Schedule("test-schedule")
95 |
96 | # try:
97 | # test_schedule.every("three days", lambda ctx: ctx)
98 | # pytest.fail()
99 | # except Exception as e:
100 | # assert str(e).startswith("invalid rate expression, expression") is True
101 |
102 | # TODO: Re-implement schedule validation
103 | # def test_every_with_missing_rate_description_rate(self):
104 | # test_schedule = Schedule("test-schedule")
105 |
106 | # try:
107 | # test_schedule.every("months", lambda ctx: ctx)
108 | # pytest.fail()
109 | # except Exception as e:
110 | # assert str(e).startswith("invalid rate expression, frequency") is True
111 |
--------------------------------------------------------------------------------
/tests/resources/test_secrets.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from unittest import IsolatedAsyncioTestCase
20 | from unittest.mock import AsyncMock, patch
21 |
22 | import pytest
23 | from grpclib import GRPCError, Status
24 |
25 | from nitric.exception import UnknownException
26 | from nitric.proto.resources.v1 import Action, PolicyResource, ResourceDeclareRequest, ResourceIdentifier, ResourceType
27 | from nitric.proto.secrets.v1 import (
28 | Secret,
29 | SecretAccessRequest,
30 | SecretAccessResponse,
31 | SecretPutRequest,
32 | SecretPutResponse,
33 | SecretVersion,
34 | )
35 | from nitric.resources.secrets import SecretRef, SecretValue, secret
36 |
37 | # pylint: disable=protected-access,missing-function-docstring,missing-class-docstring
38 |
39 |
40 | class SecretsClientTest(IsolatedAsyncioTestCase):
41 | async def test_put(self):
42 | mock_put = AsyncMock()
43 | mock_response = SecretPutResponse(
44 | secret_version=SecretVersion(secret=Secret(name="test-secret"), version="test-version")
45 | )
46 | mock_put.return_value = mock_response
47 |
48 | with patch("nitric.proto.secrets.v1.SecretManagerStub.put", mock_put):
49 | secret = SecretRef("test-secret")
50 | result = await secret.put(b"a test secret value")
51 |
52 | # Check expected values were passed to Stub
53 | mock_put.assert_called_once_with(
54 | secret_put_request=SecretPutRequest(secret=Secret(name="test-secret"), value=b"a test secret value")
55 | )
56 |
57 | # Check the returned value
58 | assert result.version == "test-version"
59 | assert result.secret.name == "test-secret"
60 |
61 | async def test_put_string(self):
62 | mock_put = AsyncMock()
63 | mock_response = SecretPutResponse(
64 | secret_version=SecretVersion(secret=Secret(name="test-secret"), version="test-version")
65 | )
66 | mock_put.return_value = mock_response
67 |
68 | with patch("nitric.proto.secrets.v1.SecretManagerStub.put", mock_put):
69 | secret = SecretRef("test-secret")
70 | await secret.put("a test secret value") # string, not bytes
71 |
72 | # Check expected values were passed to Stub
73 | mock_put.assert_called_once_with(
74 | secret_put_request=SecretPutRequest(secret=Secret(name="test-secret"), value=b"a test secret value")
75 | )
76 |
77 | async def test_latest(self):
78 | version = SecretRef("test-secret").latest()
79 |
80 | assert version.secret.name == "test-secret"
81 | assert version.version == "latest"
82 |
83 | async def test_access(self):
84 | mock_access = AsyncMock()
85 | mock_response = SecretAccessResponse(
86 | secret_version=SecretVersion(secret=Secret(name="test-secret"), version="response-version"),
87 | value=b"super secret value",
88 | )
89 | mock_access.return_value = mock_response
90 |
91 | with patch("nitric.proto.secrets.v1.SecretManagerStub.access", mock_access):
92 | version = SecretRef("test-secret").latest()
93 | result = await version.access()
94 |
95 | # Check expected values were passed to Stub
96 | mock_access.assert_called_once_with(
97 | secret_access_request=SecretAccessRequest(
98 | secret_version=SecretVersion(secret=Secret(name="test-secret"), version="latest")
99 | )
100 | )
101 |
102 | # Check the returned value
103 | assert result.version.version == "response-version"
104 | assert result.value == b"super secret value"
105 |
106 | async def test_value_to_string(self):
107 | value = SecretValue(version=None, value=b"secret value")
108 |
109 | assert value.as_string() == "secret value"
110 | assert str(value) == "secret value"
111 |
112 | async def test_value_to_bytes(self):
113 | value = SecretValue(version=None, value=b"secret value")
114 |
115 | assert value.as_bytes() == b"secret value"
116 | assert bytes(value) == b"secret value"
117 |
118 | async def test_put_error(self):
119 | mock_put = AsyncMock()
120 | mock_put.side_effect = GRPCError(Status.UNKNOWN, "test error")
121 |
122 | with patch("nitric.proto.secrets.v1.SecretManagerStub.put", mock_put):
123 | with pytest.raises(UnknownException) as e:
124 | secret = SecretRef("test-secret")
125 | await secret.put(b"a test secret value")
126 |
127 | async def test_access_error(self):
128 | mock_access = AsyncMock()
129 | mock_access.side_effect = GRPCError(Status.UNKNOWN, "test error")
130 |
131 | with patch("nitric.proto.secrets.v1.SecretManagerStub.access", mock_access):
132 | with pytest.raises(UnknownException) as e:
133 | await SecretRef("test-secret").latest().access()
134 |
135 |
136 | class Object(object):
137 | pass
138 |
139 |
140 | class SecretTest(IsolatedAsyncioTestCase):
141 | def test_allow_put(self):
142 | mock_declare = AsyncMock()
143 | mock_response = Object()
144 | mock_declare.return_value = mock_response
145 |
146 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
147 | secret("test-secret").allow("put")
148 |
149 | # Check expected values were passed to Stub
150 | mock_declare.assert_called_with(
151 | resource_declare_request=ResourceDeclareRequest(
152 | id=ResourceIdentifier(type=ResourceType.Policy),
153 | policy=PolicyResource(
154 | principals=[ResourceIdentifier(type=ResourceType.Service)],
155 | actions=[Action.SecretPut],
156 | resources=[ResourceIdentifier(type=ResourceType.Secret, name="test-secret")],
157 | ),
158 | )
159 | )
160 |
161 | def test_allow_access(self):
162 | mock_declare = AsyncMock()
163 | mock_response = Object()
164 | mock_declare.return_value = mock_response
165 |
166 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
167 | secret("test-secret").allow("access")
168 |
169 | # Check expected values were passed to Stub
170 | mock_declare.assert_called_with(
171 | resource_declare_request=ResourceDeclareRequest(
172 | id=ResourceIdentifier(type=ResourceType.Policy),
173 | policy=PolicyResource(
174 | principals=[ResourceIdentifier(type=ResourceType.Service)],
175 | actions=[Action.SecretAccess],
176 | resources=[ResourceIdentifier(type=ResourceType.Secret, name="test-secret")],
177 | ),
178 | )
179 | )
180 |
--------------------------------------------------------------------------------
/tests/resources/test_sql.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from unittest import IsolatedAsyncioTestCase
20 | from unittest.mock import AsyncMock, Mock, patch
21 |
22 | import pytest
23 |
24 | from nitric.proto.resources.v1 import (
25 | ResourceDeclareRequest,
26 | ResourceIdentifier,
27 | ResourceType,
28 | SqlDatabaseResource,
29 | )
30 | from nitric.resources import sql
31 |
32 | # pylint: disable=protected-access,missing-function-docstring,missing-class-docstring
33 |
34 |
35 | class Object(object):
36 | pass
37 |
38 |
39 | class MockAsyncChannel:
40 | def __init__(self):
41 | self.send = AsyncMock()
42 | self.close = Mock()
43 | self.done = Mock()
44 |
45 |
46 | class SqlTest(IsolatedAsyncioTestCase):
47 | def test_declare_sql(self):
48 | mock_declare = AsyncMock()
49 | mock_response = Object()
50 | mock_declare.return_value = mock_response
51 |
52 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
53 | sqldb = sql("test-sql")
54 |
55 | # Check expected values were passed to Stub
56 | mock_declare.assert_called_with(
57 | resource_declare_request=ResourceDeclareRequest(
58 | id=ResourceIdentifier(name="test-sql", type=ResourceType.SqlDatabase),
59 | sql_database=SqlDatabaseResource(),
60 | )
61 | )
62 |
--------------------------------------------------------------------------------
/tests/resources/test_topics.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from unittest import IsolatedAsyncioTestCase
20 | from unittest.mock import AsyncMock, Mock, patch
21 |
22 | import pytest
23 | from grpclib import GRPCError, Status
24 |
25 | from nitric.exception import UnknownException
26 | from nitric.proto.resources.v1 import Action, PolicyResource, ResourceDeclareRequest, ResourceIdentifier, ResourceType
27 | from nitric.proto.topics.v1 import TopicMessage, TopicPublishRequest
28 | from nitric.resources import topic
29 | from nitric.resources.topics import TopicRef
30 | from nitric.utils import struct_from_dict
31 |
32 | # pylint: disable=protected-access,missing-function-docstring,missing-class-docstring
33 |
34 |
35 | class Object(object):
36 | pass
37 |
38 |
39 | class EventClientTest(IsolatedAsyncioTestCase):
40 | async def test_publish(self):
41 | mock_publish = AsyncMock()
42 | mock_response = Object()
43 | mock_publish.return_value = mock_response
44 |
45 | payload = {"content": "of event"}
46 |
47 | with patch("nitric.proto.topics.v1.TopicsStub.publish", mock_publish):
48 | topic = TopicRef("test-topic")
49 | await topic.publish(payload)
50 |
51 | # Check expected values were passed to Stub
52 | # mock_publish.assert_called_once()
53 | mock_publish.assert_called_once_with(
54 | topic_publish_request=TopicPublishRequest(
55 | topic_name="test-topic", message=TopicMessage(struct_payload=struct_from_dict(payload))
56 | )
57 | )
58 |
59 | async def test_publish_invalid_type(self):
60 | mock_publish = AsyncMock()
61 | mock_response = Object()
62 | mock_publish.return_value = mock_response
63 |
64 | with patch("nitric.proto.topics.v1.TopicsStub.publish", mock_publish):
65 | topic = TopicRef("test-topic")
66 | with pytest.raises(ValueError):
67 | await topic.publish((1, 2, 3))
68 |
69 | async def test_publish_error(self):
70 | mock_publish = AsyncMock()
71 | mock_publish.side_effect = GRPCError(Status.UNKNOWN, "test error")
72 |
73 | with patch("nitric.proto.topics.v1.TopicsStub.publish", mock_publish):
74 | with pytest.raises(UnknownException):
75 | await TopicRef("test-topic").publish({})
76 |
77 |
78 | class Object(object):
79 | pass
80 |
81 |
82 | class MockAsyncChannel:
83 | def __init__(self):
84 | self.send = AsyncMock()
85 | self.close = Mock()
86 | self.done = Mock()
87 |
88 |
89 | class TopicTest(IsolatedAsyncioTestCase):
90 | def test_create_allow_publishing(self):
91 | mock_declare = AsyncMock()
92 | mock_response = Object()
93 | mock_declare.return_value = mock_response
94 |
95 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
96 | topic("test-topic").allow("publish")
97 |
98 | # Check expected values were passed to Stub
99 | mock_declare.assert_called_with(
100 | resource_declare_request=ResourceDeclareRequest(
101 | id=ResourceIdentifier(type=ResourceType.Policy),
102 | policy=PolicyResource(
103 | principals=[ResourceIdentifier(type=ResourceType.Service)],
104 | actions=[Action.TopicPublish],
105 | resources=[ResourceIdentifier(type=ResourceType.Topic, name="test-topic")],
106 | ),
107 | )
108 | )
109 |
--------------------------------------------------------------------------------
/tests/resources/test_websockets.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from unittest import IsolatedAsyncioTestCase
20 | from unittest.mock import AsyncMock, Mock, patch
21 |
22 | from nitric.proto.resources.v1 import Action, PolicyResource, ResourceDeclareRequest, ResourceIdentifier, ResourceType
23 | from nitric.proto.websockets.v1 import WebsocketSendRequest
24 | from nitric.resources import Websocket, websocket
25 | from nitric.resources.websockets import WebsocketRef
26 |
27 | # pylint: disable=protected-access,missing-function-docstring,missing-class-docstring
28 |
29 |
30 | class Object(object):
31 | pass
32 |
33 |
34 | class WebsocketClientTest(IsolatedAsyncioTestCase):
35 | async def test_send(self):
36 | mock_send = AsyncMock()
37 | mock_response = Object()
38 | mock_send.return_value = mock_response
39 | test_data = b"test-data"
40 |
41 | with patch("nitric.proto.websockets.v1.WebsocketStub.send_message", mock_send):
42 | await WebsocketRef().send("test-socket", "test-connection", test_data)
43 |
44 | # Check expected values were passed to Stub
45 | mock_send.assert_called_once_with(
46 | websocket_send_request=WebsocketSendRequest(
47 | socket_name="test-socket", connection_id="test-connection", data=test_data
48 | )
49 | )
50 |
51 |
52 | class MockAsyncChannel:
53 | def __init__(self):
54 | self.send = AsyncMock()
55 | self.close = Mock()
56 | self.done = Mock()
57 |
58 |
59 | class WebsocketTest(IsolatedAsyncioTestCase):
60 | def test_create(self):
61 | mock_declare = AsyncMock()
62 | mock_response = Object()
63 | mock_declare.return_value = mock_response
64 |
65 | with patch("nitric.proto.resources.v1.ResourcesStub.declare", mock_declare):
66 | websocket("test-websocket")
67 |
68 | # Check expected values were passed to Stub
69 | mock_declare.assert_called_with(
70 | resource_declare_request=ResourceDeclareRequest(
71 | id=ResourceIdentifier(type=ResourceType.Policy),
72 | policy=PolicyResource(
73 | principals=[ResourceIdentifier(type=ResourceType.Service)],
74 | actions=[Action.WebsocketManage],
75 | resources=[ResourceIdentifier(type=ResourceType.Websocket, name="test-websocket")],
76 | ),
77 | )
78 | )
79 |
80 |
81 | class WebsocketClientTest(IsolatedAsyncioTestCase):
82 | async def test_send(self):
83 | mock_send = AsyncMock()
84 | mock_response = Object()
85 | mock_send.return_value = mock_response
86 | test_data = b"test-data"
87 |
88 | with patch("nitric.proto.websockets.v1.WebsocketStub.send_message", mock_send):
89 | await Websocket("testing").send("test-connection", test_data)
90 |
91 | # Check expected values were passed to Stub
92 | mock_send.assert_called_once_with(
93 | websocket_send_request=WebsocketSendRequest(
94 | socket_name="testing", connection_id="test-connection", data=test_data
95 | )
96 | )
97 |
--------------------------------------------------------------------------------
/tests/test__utils.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | import copy
20 |
21 | from nitric.utils import struct_from_dict, dict_from_struct
22 |
23 |
24 | def test__dict_from_struct():
25 | dict_val = {
26 | "a": True,
27 | "b": False,
28 | "c": 1,
29 | "d": 4.123,
30 | "e": "this is a string",
31 | "f": None,
32 | "g": {"ga": 1, "gb": "testing inner dict"},
33 | "h": [1, 2, 3, "four"],
34 | }
35 | dict_copy = copy.deepcopy(dict_val)
36 |
37 | # Serialization and Deserialization shouldn't modify the object in most cases.
38 | assert dict_from_struct(struct_from_dict(dict_val)) == dict_copy
39 |
--------------------------------------------------------------------------------
/tests/test_application.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | from unittest import IsolatedAsyncioTestCase
20 | from unittest.mock import patch, AsyncMock, Mock
21 |
22 | import pytest
23 |
24 |
25 | from nitric.exception import NitricUnavailableException
26 | from nitric.proto.deployments.v1 import ScheduleEvery
27 | from nitric.proto.schedules.v1 import RegistrationRequest
28 | from nitric.resources import Bucket, schedule
29 | from nitric.application import Nitric
30 |
31 |
32 | class Object(object):
33 | pass
34 |
35 |
36 | class MockAsyncChannel:
37 | def __init__(self):
38 | self.send = AsyncMock()
39 | self.close = Mock()
40 | self.done = Mock()
41 |
42 |
43 | class ApplicationTest(IsolatedAsyncioTestCase):
44 | def test_create_resource(self):
45 | application = Nitric()
46 | mock_make = Mock()
47 | mock_make.side_effect = ConnectionRefusedError("test error")
48 |
49 | with patch("nitric.resources.resource.Resource.make", mock_make):
50 | try:
51 | application._create_resource(Bucket, "test-bucket")
52 | except NitricUnavailableException as e:
53 | assert str(e).startswith("Unable to connect")
54 |
55 | def test_run_with_no_active_event_loop(self):
56 | application = Nitric()
57 |
58 | mock_running_loop = Mock()
59 | mock_running_loop.side_effect = RuntimeError("loop is not running")
60 |
61 | mock_event_loop = Mock()
62 |
63 | with patch("asyncio.get_event_loop", mock_event_loop):
64 | with patch("asyncio.get_running_loop", mock_running_loop):
65 | application.run()
66 |
67 | mock_running_loop.assert_called_once()
68 | mock_event_loop.assert_called_once()
69 |
70 | def test_run_with_keyboard_interrupt(self):
71 | application = Nitric()
72 |
73 | mock_running_loop = Mock()
74 | mock_running_loop.side_effect = KeyboardInterrupt("cancel")
75 |
76 | mock_event_loop = Mock()
77 |
78 | with patch("asyncio.get_event_loop", mock_event_loop):
79 | with patch("asyncio.get_running_loop", mock_running_loop):
80 | application.run()
81 |
82 | mock_running_loop.assert_called_once()
83 | mock_event_loop.assert_not_called()
84 |
85 | def test_run_with_connection_refused(self):
86 | application = Nitric()
87 |
88 | mock_running_loop = Mock()
89 | mock_running_loop.side_effect = ConnectionRefusedError("refusing connection")
90 |
91 | mock_event_loop = Mock()
92 |
93 | with patch("asyncio.get_event_loop", mock_event_loop):
94 | with patch("asyncio.get_running_loop", mock_running_loop):
95 | try:
96 | application.run()
97 | pytest.fail()
98 | except NitricUnavailableException as e:
99 | assert str(e).startswith("Unable to connect to nitric server")
100 |
101 | mock_running_loop.assert_called_once()
102 | mock_event_loop.assert_not_called()
103 |
--------------------------------------------------------------------------------
/tests/test_exception.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2021 Nitric Technologies Pty Ltd.
3 | #
4 | # This file is part of Nitric Python 3 SDK.
5 | # See https://github.com/nitrictech/python-sdk for further info.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | import pytest
21 | from grpclib import GRPCError, Status
22 |
23 | from nitric.exception import (
24 | CancelledException,
25 | exception_from_grpc_error,
26 | _exception_code_map,
27 | UnknownException,
28 | InvalidArgumentException,
29 | DeadlineExceededException,
30 | NotFoundException,
31 | AlreadyExistsException,
32 | PermissionDeniedException,
33 | ResourceExhaustedException,
34 | FailedPreconditionException,
35 | AbortedException,
36 | OutOfRangeException,
37 | UnimplementedException,
38 | InternalException,
39 | UnavailableException,
40 | DataLossException,
41 | UnauthenticatedException,
42 | exception_from_grpc_code,
43 | )
44 |
45 |
46 | expectedMapping = [
47 | (0, Exception),
48 | (1, CancelledException),
49 | (2, UnknownException),
50 | (3, InvalidArgumentException),
51 | (4, DeadlineExceededException),
52 | (5, NotFoundException),
53 | (6, AlreadyExistsException),
54 | (7, PermissionDeniedException),
55 | (8, ResourceExhaustedException),
56 | (9, FailedPreconditionException),
57 | (10, AbortedException),
58 | (11, OutOfRangeException),
59 | (12, UnimplementedException),
60 | (13, InternalException),
61 | (14, UnavailableException),
62 | (15, DataLossException),
63 | (16, UnauthenticatedException),
64 | ]
65 |
66 |
67 | class TestException:
68 | @pytest.fixture(autouse=True)
69 | def init_exceptions(self):
70 | # Status codes that can be automatically converted to exceptions
71 | self.accepted_status_codes = set(_exception_code_map.keys())
72 |
73 | # Ensure none of the status are missing from the test cases
74 | assert set(k for k, v in expectedMapping) == self.accepted_status_codes
75 |
76 | def test_all_codes_handled(self):
77 | # Status codes defined by betterproto
78 | all_grpc_status_codes = set(status.value for status in Status)
79 |
80 | assert all_grpc_status_codes == self.accepted_status_codes
81 |
82 | @pytest.mark.parametrize("test_value", expectedMapping)
83 | def test_grpc_code_to_exception(self, test_value):
84 | status, expected_class = test_value
85 | exception = exception_from_grpc_error(GRPCError(Status(status), "some error"))
86 |
87 | assert isinstance(exception, expected_class)
88 |
89 | def test_unexpected_status_code(self):
90 | assert isinstance(exception_from_grpc_code(100), UnknownException)
91 |
--------------------------------------------------------------------------------
/tools/apache-2.tmpl:
--------------------------------------------------------------------------------
1 | Copyright (c) ${years} ${owner}.
2 |
3 | This file is part of ${projectname}.
4 | See ${projecturl} for further info.
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = linter,py3.11
3 |
4 | [testenv]
5 | deps =
6 | -e .[dev]
7 | commands =
8 | pytest --cov=./nitric --cov-report=xml
9 |
10 | [testenv:linter]
11 | deps =
12 | flake8
13 | flake8-bugbear
14 | flake8-comprehensions
15 | flake8-string-format
16 | black
17 | pydocstyle
18 | pip-licenses
19 | commands =
20 | flake8 nitric
21 | black nitric tests tools
22 | pydocstyle nitric
23 | pip-licenses --allow-only="MIT License;BSD License;Zope Public License;Python Software Foundation License;Apache License 2.0;Apache Software License;MIT License, Mozilla Public License 2.0 (MPL 2.0);MIT;BSD License, Apache Software License;3-Clause BSD License;Historical Permission Notice and Disclaimer (HPND);Mozilla Public License 2.0 (MPL 2.0);Apache Software License, BSD License;BSD;Python Software Foundation License, MIT License;Public Domain;Public Domain, Python Software Foundation License, BSD License, GNU General Public License (GPL);GNU Library or Lesser General Public License (LGPL);LGPL;Apache Software License, MIT License" --ignore-packages nitric nitric-api asyncio
24 |
25 | [flake8]
26 | exclude =
27 | venv
28 | tests
29 | build
30 | dist
31 | .git
32 | .tox
33 | nitric/proto
34 | examples
35 | testproj
36 | ignore = F821, W503, F723
37 | max-line-length = 120
38 |
39 | [pydocstyle]
40 | ignore = D100, D105, D203, D212, D415
41 | match = (?!(test_|setup)).*\.py
42 | match_dir = (?!(venv|build|examples|dist|tests|.git|.tox|proto)).*
--------------------------------------------------------------------------------