Docs - Extend Development Tips (#4545)
This commit is contained in:
parent
f4c4b349c7
commit
669e7048f1
@ -1,118 +0,0 @@
|
||||
.. _contributing tips:
|
||||
|
||||
=============================
|
||||
Development Tips
|
||||
=============================
|
||||
|
||||
Below you can find some tips that might help during development.
|
||||
|
||||
****************************
|
||||
Naming Conventions
|
||||
****************************
|
||||
Let's say you want to implement the `import_certificate` feature for the ACM service.
|
||||
|
||||
Implementing the feature itself can be done by creating a method called `import_certificate` in `moto/acm/responses.py`.
|
||||
| It's considered good practice to deal with input/output formatting and validation in `responses.py`, and create a method `import_certificate` in `moto/acm/models.py` that handles the actual import logic.
|
||||
|
||||
When writing tests, you'll want to add a new method called `def test_import_certificate` to `tests/test_acm/test_acm.py`.
|
||||
| Additional tests should also have names indicate of what's happening, i.e. `def test_import_certificate_fails_without_name`, `def test_import_existing_certificate`, etc.
|
||||
|
||||
|
||||
****************************************
|
||||
Determining which URLs to intercept
|
||||
****************************************
|
||||
In order for Moto to know which requests to intercept, Moto needs to know which URLs to intercept.
|
||||
| But how do we know which URL's should be intercepted? There are a few ways of doing it:
|
||||
|
||||
- For an existing service, copy/paste the url-path for an existing feature and cross your fingers and toes
|
||||
- Use the service model that is used by botocore: https://github.com/boto/botocore/tree/develop/botocore/data
|
||||
Look for the `requestUri`-field in the `services.json` file.
|
||||
- Make a call to AWS itself, and intercept the request using a proxy.
|
||||
This gives you all information you could need, including the URL, parameters, request and response format.
|
||||
|
||||
|
||||
******************************
|
||||
Intercepting AWS requests
|
||||
******************************
|
||||
Download and install a proxy such `MITMProxy <https://mitmproxy.org/>`_.
|
||||
|
||||
With the proxy running, the easiest way of proxying requests to AWS is probably via the CLI.
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
export HTTP_PROXY=http://localhost:8080
|
||||
export HTTPS_PROXY=http://localhost:8080
|
||||
aws ses describe-rule-set --no-verify-ssl
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from botocore.config import Config
|
||||
proxy_config = Config(proxies={'http': 'localhost:8080', 'https': 'localhost:8080'})
|
||||
boto3.client("ses", config=proxy_config, use_ssl=False, verify=False)
|
||||
|
||||
|
||||
******************************
|
||||
Tagging Service
|
||||
******************************
|
||||
|
||||
A dedicated TaggingService exists in `moto.utilities`, to help with storing/retrieving tags for resources.
|
||||
|
||||
Not all services use it yet, but contributors are encouraged to use the TaggingService for all new features.
|
||||
|
||||
***************************
|
||||
CI
|
||||
***************************
|
||||
|
||||
Our CI runs all tests twice - one normal run, and one run in ServerMode. In ServerMode, Moto is started as a stand-alone Flask server, and all tests are run against this Flask-instance.
|
||||
|
||||
To verify whether your tests pass in ServerMode, you can run the following commands:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
moto_server
|
||||
TEST_SERVER_MODE=true pytest -sv tests/test_service/..
|
||||
|
||||
|
||||
*****************************
|
||||
Partial Implementations
|
||||
*****************************
|
||||
|
||||
If a service is only partially implemented, a warning can be used to inform the user:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
import warnings
|
||||
warnings.warn("The Filters-parameter is not yet implemented for client.method()")
|
||||
|
||||
|
||||
****************
|
||||
Writing tests
|
||||
****************
|
||||
|
||||
One test should only verify a single feature/method. I.e., one test for `create_resource()`, another for `update_resource()`, etc.
|
||||
|
||||
Negative tests
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
When writing negative tests, try to use the following format:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.failing_call(..)
|
||||
err = exc.value.response["Error"]
|
||||
# These assertions use the 'sure' library, but any assertion style is accepted
|
||||
err["Code"].should.equal(..)
|
||||
err["Message"].should.equal(..)
|
||||
|
||||
This is the best way to ensure that exceptions are dealt with correctly by Moto.
|
||||
|
||||
|
||||
Parallel tests
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To speed up our CI, the ServerMode tests for the `awslambda`, `batch`, `ec2` and `sqs` services will run in parallel.
|
||||
This means the following:
|
||||
|
||||
- Make sure you use unique names for functions/queues/etc
|
||||
- Calls to `describe_reservations()`/`list_queues()`/etc might return resources from other tests
|
36
docs/docs/contributing/development_tips/index.rst
Normal file
36
docs/docs/contributing/development_tips/index.rst
Normal file
@ -0,0 +1,36 @@
|
||||
.. _contributing tips:
|
||||
|
||||
|
||||
.. role:: raw-html(raw)
|
||||
:format: html
|
||||
|
||||
|
||||
=============================
|
||||
Development Tips
|
||||
=============================
|
||||
|
||||
Below you can find some tips that might help during development.
|
||||
|
||||
Naming Conventions
|
||||
****************************
|
||||
|
||||
Let's say you want to implement the `import_certificate` feature for the ACM service.
|
||||
|
||||
Implementing the feature itself can be done by creating a method called `import_certificate` in `moto/acm/responses.py`. :raw-html:`<br />`
|
||||
It's considered good practice to deal with input/output formatting and validation in `responses.py`, and create a method `import_certificate` in `moto/acm/models.py` that handles the actual import logic.
|
||||
|
||||
When writing tests, you'll want to add a new method called `def test_import_certificate` to `tests/test_acm/test_acm.py`. :raw-html:`<br />`
|
||||
Additional tests should also have names indicate of what's happening, i.e. `def test_import_certificate_fails_without_name`, `def test_import_existing_certificate`, etc.
|
||||
|
||||
|
||||
|
||||
Partial Implementations
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If a service is only partially implemented, a warning can be used to inform the user:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
import warnings
|
||||
warnings.warn("The Filters-parameter is not yet implemented for client.method()")
|
||||
|
48
docs/docs/contributing/development_tips/tests.rst
Normal file
48
docs/docs/contributing/development_tips/tests.rst
Normal file
@ -0,0 +1,48 @@
|
||||
.. _contributing tests:
|
||||
|
||||
|
||||
****************
|
||||
Writing tests
|
||||
****************
|
||||
|
||||
One test should only verify a single feature/method. I.e., one test for `create_resource()`, another for `update_resource()`, etc.
|
||||
|
||||
Negative tests
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
When writing negative tests, try to use the following format:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
with pytest.raises(botocore.exceptions.ClientError) as exc:
|
||||
client.failing_call(..)
|
||||
err = exc.value.response["Error"]
|
||||
# These assertions use the 'sure' library, but any assertion style is accepted
|
||||
err["Code"].should.equal(..)
|
||||
err["Message"].should.equal(..)
|
||||
|
||||
This is the best way to ensure that exceptions are dealt with correctly by Moto.
|
||||
|
||||
|
||||
ServerMode tests
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Our CI runs all tests twice - one normal run, and one run in ServerMode. In ServerMode, Moto is started as a stand-alone Flask server, and all tests are run against this Flask-instance.
|
||||
|
||||
To verify whether your tests pass in ServerMode, you can run the following commands:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
moto_server
|
||||
TEST_SERVER_MODE=true pytest -sv tests/test_service/..
|
||||
|
||||
|
||||
Parallel tests
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To speed up our CI, the ServerMode tests for the `awslambda`, `batch`, `ec2` and `sqs` services will run in parallel.
|
||||
This means the following:
|
||||
|
||||
- Make sure you use unique names for functions/queues/etc
|
||||
- Calls to `describe_reservations()`/`list_queues()`/etc might return resources from other tests
|
||||
|
39
docs/docs/contributing/development_tips/urls.rst
Normal file
39
docs/docs/contributing/development_tips/urls.rst
Normal file
@ -0,0 +1,39 @@
|
||||
.. _contributing urls:
|
||||
|
||||
***********************
|
||||
Intercepting URL's
|
||||
***********************
|
||||
|
||||
|
||||
Determining which URLs to intercept
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In order for Moto to know which requests to intercept, Moto needs to know which URLs to intercept.
|
||||
| But how do we know which URL's should be intercepted? There are a few ways of doing it:
|
||||
|
||||
- For an existing service, copy/paste the url-path for an existing feature and cross your fingers and toes
|
||||
- Use the service model that is used by botocore: https://github.com/boto/botocore/tree/develop/botocore/data
|
||||
Look for the `requestUri`-field in the `services.json` file.
|
||||
- Make a call to AWS itself, and intercept the request using a proxy.
|
||||
This gives you all information you could need, including the URL, parameters, request and response format.
|
||||
|
||||
|
||||
Intercepting AWS requests
|
||||
***************************
|
||||
|
||||
Download and install a proxy such `MITMProxy <https://mitmproxy.org/>`_.
|
||||
|
||||
With the proxy running, the easiest way of proxying requests to AWS is probably via the CLI.
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
export HTTP_PROXY=http://localhost:8080
|
||||
export HTTPS_PROXY=http://localhost:8080
|
||||
aws ses describe-rule-set --no-verify-ssl
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from botocore.config import Config
|
||||
proxy_config = Config(proxies={'http': 'localhost:8080', 'https': 'localhost:8080'})
|
||||
boto3.client("ses", config=proxy_config, use_ssl=False, verify=False)
|
||||
|
80
docs/docs/contributing/development_tips/utilities.rst
Normal file
80
docs/docs/contributing/development_tips/utilities.rst
Normal file
@ -0,0 +1,80 @@
|
||||
.. _contributing utilities:
|
||||
|
||||
=============================
|
||||
Utilities
|
||||
=============================
|
||||
|
||||
Tagging Service
|
||||
******************************
|
||||
|
||||
A dedicated TaggingService exists in `moto.utilities`, to help with storing/retrieving tags for resources.
|
||||
|
||||
Not all services use it yet, but contributors are encouraged to use the TaggingService for all new features.
|
||||
|
||||
|
||||
Paginator
|
||||
***********
|
||||
|
||||
When requesting a list of resources, almost all AWS services use pagination to stagger the response.
|
||||
Moto provides a utility class to automatically paginate a response, without having to manually write the logic.
|
||||
|
||||
There are three components that make this work:
|
||||
|
||||
#. The Responses-method will call the backend with a max_result/next_token parameter
|
||||
#. The backend-method has a plain method that's decorated with `@paginate`
|
||||
#. A configuration model is supplied to the decorator that contains all the details.
|
||||
|
||||
See the below example how it works in practice:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class MyResponse(BaseResponse):
|
||||
|
||||
# The Response-class looks like any other - read the input parameters, and call the backend to retrieve the resources
|
||||
def list_resources():
|
||||
max_results = 100
|
||||
next_token = self._get_param("NextToken")
|
||||
# Note that we're getting both the results and the next_token
|
||||
# The decorator in the backend returns this tuple
|
||||
paged_results, next_token = self.backend.list_resources(
|
||||
max_results=max_results, next_token=next_token
|
||||
)
|
||||
...
|
||||
|
||||
from moto.utilities.paginator import paginate
|
||||
class MyBackend(BaseBackend):
|
||||
|
||||
# The model that contains the configuration required for the paginator
|
||||
PAGINATION_MODEL = {
|
||||
# key = name of the method in the backend
|
||||
"list_resources": {
|
||||
#
|
||||
# name of the kwarg that contains the next token, which should be passed to the backend
|
||||
# backend.list_resources(next_token=..)
|
||||
"input_token": "next_token",
|
||||
#
|
||||
# name of the kwarg that contains the max number of results, which should be passed to the backend
|
||||
"limit_key": "max_results",
|
||||
#
|
||||
# The default limit of the above parameter is not provided
|
||||
"limit_default": 100,
|
||||
#
|
||||
# The collection of keys/attributes that should guarantee uniqueness for a given resource.
|
||||
# For most resources it will just be an ID, or ARN, which is always unique.
|
||||
# In order to know what is the last resource that we're sending back, an encoded version of these attributes is used as the NextToken.
|
||||
"page_ending_range_keys": ["start_date", "execution_arn"],
|
||||
},
|
||||
# another method that will use different configuration options
|
||||
"list_other_things": {
|
||||
...
|
||||
},
|
||||
}
|
||||
|
||||
# The decorator with the pagination logic
|
||||
@paginate(pagination_model=PAGINATION_MODEL)
|
||||
# Note that this method does not list the 'next_token'/'max_results'-arguments
|
||||
# The decorator uses them, but our logic doesn't need them
|
||||
def list_resources(self):
|
||||
# Note that we simply return all resources - the decorator takes care of all pagination magic
|
||||
return self.full_list_of_resources
|
||||
|
@ -1,16 +1,27 @@
|
||||
.. _contributing faq:
|
||||
|
||||
.. role:: bash(code)
|
||||
:language: bash
|
||||
|
||||
.. role:: python(code)
|
||||
:language: python
|
||||
|
||||
=============================
|
||||
FAQ
|
||||
=============================
|
||||
|
||||
Is Moto concurrency safe?
|
||||
############################
|
||||
|
||||
No. Moto is not designed for multithreaded access/multiprocessing.
|
||||
|
||||
When running the linter...
|
||||
#############################
|
||||
|
||||
Why does black give different results?
|
||||
****************************************
|
||||
Different versions of black produce different results.
|
||||
To ensure that our CI passes, please format the code using `black==19.10b0`.
|
||||
To ensure that our CI passes, please format the code using :bash:`black==19.10b0`.
|
||||
|
||||
When running a test...
|
||||
#########################
|
||||
@ -25,49 +36,12 @@ If the test uses Docker, it could take a while to:
|
||||
- Wait for the logs to appear
|
||||
|
||||
|
||||
Why am I getting Docker errors?
|
||||
********************************
|
||||
AWSLambda and Batch services use Docker to execute the code provided to the system, which means that Docker needs to be installed on your system in order for these tests to run.
|
||||
|
||||
|
||||
Why are my tests failing in ServerMode?
|
||||
******************************************
|
||||
- Make sure that the correct url paths are present in `urls.py`
|
||||
- Make sure that you've run `scripts/update_backend_index.py` afterwards, to let MotoServer know the urls have changed.
|
||||
- Make sure that you've run :bash:`scripts/update_backend_index.py` afterwards, to let MotoServer know the urls have changed.
|
||||
|
||||
|
||||
When using the scaffolding scripts..
|
||||
#######################################
|
||||
|
||||
Why am I getting the error that my new module could not be found?
|
||||
*******************************************************************
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
File "scripts/scaffold.py", line 499, in insert_codes
|
||||
insert_code_to_class(responses_path, BaseResponse, func_in_responses)
|
||||
File "scripts/scaffold.py", line 424, in insert_code_to_class
|
||||
mod = importlib.import_module(mod_path)
|
||||
File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module
|
||||
return _bootstrap._gcd_import(name[level:], package, level)
|
||||
File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
|
||||
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
|
||||
File "<frozen importlib._bootstrap>", line 961, in _find_and_load_unlocked
|
||||
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
|
||||
File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
|
||||
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
|
||||
File "<frozen importlib._bootstrap>", line 973, in _find_and_load_unlocked
|
||||
ModuleNotFoundError: No module named 'moto.kafka'
|
||||
|
||||
This will happen if you've ran `pip install .` prior to running `scripts/scaffold.py`.
|
||||
|
||||
Instead, install Moto as an editable module instead:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
pip uninstall moto
|
||||
pip install -e .
|
||||
|
||||
|
||||
What ...
|
||||
#################
|
||||
@ -89,7 +63,7 @@ There are three types of tests:
|
||||
The decorator tests refer to the normal unit tests that are run against an in-memory Moto instance.
|
||||
|
||||
The ServerMode tests refer to the same set of tests - but run against an external MotoServer instance.
|
||||
When running tests in ServerMode, each boto3-client and boto3-resource are intercepted, and enriched with the `endpoint_url="http://localhost:5000"` argument. This allows us to run the same test twice, and verify that Moto behaves the same when using decorators, and in ServerMode.
|
||||
When running tests in ServerMode, each boto3-client and boto3-resource are intercepted, and enriched with the :python:`endpoint_url="http://localhost:5000"` argument. This allows us to run the same test twice, and verify that Moto behaves the same when using decorators, and in ServerMode.
|
||||
|
||||
The last 'server' tests are low-level tests that can be used to verify that Moto behaves exactly like the AWS HTTP API.
|
||||
Each test will spin up the MotoServer in memory, and run HTTP requests directly against that server.
|
||||
@ -101,3 +75,45 @@ The best alternative would be `LocalStack <https://localstack.cloud//>`_.
|
||||
|
||||
LocalStack is Moto's bigger brother with more advanced features, such as EC2 VM's that you can SSH into and Dockerized RDS-installations that you can connect to.
|
||||
|
||||
|
||||
Why am I getting errors...
|
||||
#############################
|
||||
|
||||
|
||||
That my new module could not be found when using the scaffold-script?
|
||||
************************************************************************
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
File "scripts/scaffold.py", line 499, in insert_codes
|
||||
insert_code_to_class(responses_path, BaseResponse, func_in_responses)
|
||||
File "scripts/scaffold.py", line 424, in insert_code_to_class
|
||||
mod = importlib.import_module(mod_path)
|
||||
File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module
|
||||
return _bootstrap._gcd_import(name[level:], package, level)
|
||||
|
||||
ModuleNotFoundError: No module named 'moto.kafka'
|
||||
|
||||
This will happen if you've ran :bash:`pip install .` prior to running `scripts/scaffold.py`.
|
||||
|
||||
Instead, install Moto as an editable module instead:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
pip uninstall moto
|
||||
pip install -e .
|
||||
|
||||
|
||||
Related to Docker when running tests?
|
||||
******************************************
|
||||
AWSLambda and Batch services use Docker to execute the code provided to the system, which means that Docker needs to be installed on your system in order for these tests to run.
|
||||
|
||||
Installing Moto using ZSH on MacOS?
|
||||
******************************************
|
||||
When using :bash:`pip install` on ZSH, you might see the following: :bash:`zsh: no matches found`. This is because ZSH requires the full module to be in quotes.
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
pip install "moto[ssm]"
|
||||
|
||||
|
||||
|
@ -190,7 +190,7 @@ Moto also comes with a stand-alone server allowing you to mock out an AWS HTTP e
|
||||
* Running on http://127.0.0.1:3000/
|
||||
|
||||
However, this method isn't encouraged if you're using ``boto3``, the best solution would be to use a decorator method.
|
||||
See :ref:`Non-Python SDK's` for more information.
|
||||
See :doc:`server_mode` for more information.
|
||||
|
||||
Recommended Usage
|
||||
-----------------
|
||||
|
@ -1,8 +1,11 @@
|
||||
.. _server_mode:
|
||||
|
||||
================
|
||||
Non-Python SDK's
|
||||
================
|
||||
.. role:: bash(code)
|
||||
:language: bash
|
||||
|
||||
================================
|
||||
Non-Python SDK's / Server Mode
|
||||
================================
|
||||
|
||||
Moto has a stand-alone server mode. This allows you to use Moto with any of the official AWS SDK's.
|
||||
|
||||
@ -38,7 +41,10 @@ interfaces with 0.0.0.0:
|
||||
Please be aware this might allow other network users to access your
|
||||
server.
|
||||
|
||||
To use Moto in your tests, you can pass an `endpoint_url` to the SDK of your choice.
|
||||
To use Moto in your tests, you can pass the `endpoint_url`-parameter to the SDK of your choice.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
In Python:
|
||||
|
||||
@ -85,16 +91,37 @@ In Terraform:
|
||||
|
||||
See the `Terraform Docs`_ for more information.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Here are some more examples:
|
||||
Other languages:
|
||||
|
||||
* `Java`_
|
||||
* `Ruby`_
|
||||
* `Javascript`_
|
||||
|
||||
|
||||
Use ServerMode using the decorators
|
||||
-------------------------------------
|
||||
|
||||
It is possible to call the MotoServer for tests that were written using decorators.
|
||||
The following environment variables can be set to achieve this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
TEST_SERVER_MODE=true
|
||||
|
||||
Whenever a mock-decorator starts, Moto will:
|
||||
|
||||
#. Send a reset-request to :bash:`http://localhost:5000`, removing all state that was kept
|
||||
#. Add the :bash:`endpoint_url` parameter to boto3, so that all requests will be made to :bash:`http://localhost:5000`.
|
||||
|
||||
Calling the reset-API ensures the same behaviour as normal decorators, where the complete state is removed.
|
||||
It is possible to keep the state in between tests, using this environment variable:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
MOTO_CALL_RESET_API=true
|
||||
|
||||
|
||||
Dashboard
|
||||
---------
|
||||
|
||||
|
@ -46,13 +46,23 @@ Additional Resources
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
:glob:
|
||||
:caption: Contributing to Moto
|
||||
|
||||
docs/contributing/index
|
||||
docs/contributing/installation
|
||||
docs/contributing/architecture
|
||||
docs/contributing/new_feature
|
||||
docs/contributing/development_tips
|
||||
docs/contributing/checklist
|
||||
docs/contributing/faq
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
:titlesonly:
|
||||
:caption: Development Tips
|
||||
|
||||
docs/contributing/development_tips/index
|
||||
docs/contributing/development_tips/urls
|
||||
docs/contributing/development_tips/tests
|
||||
docs/contributing/development_tips/utilities
|
||||
|
Loading…
Reference in New Issue
Block a user