From 55edc0d880cc03155a003f578879718d6dfb9364 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Mon, 9 Oct 2023 18:06:07 -0700 Subject: [PATCH 01/16] Added support for Python 3.12 and removed support for insecure versions of Python * Added Python 3.12 to test suite * Removed support for Python 3.7. Per Python maintainers, 3.7 is EOL and no longer receiving security updates * Excluded additional files that need not trigger tests. * Optimized error checking and introduced common.py enabling method extractions for common code. * Minor import optimizations * Minor linting fixes * Fixed missing NETWORK_DELETE_RETRY_WAIT_TIME argument in meraki/__init__.py * Minor syntactical improvements for README.md --- .github/workflows/test-library.yml | 19 +++++++++++++++-- README.md | 13 +++++------- meraki/__init__.py | 2 +- meraki/aio/rest_session.py | 28 ++++++------------------ meraki/common.py | 19 +++++++++++++++++ meraki/exceptions.py | 16 +++++++++++++- meraki/rest_session.py | 34 ++++++++---------------------- 7 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 meraki/common.py diff --git a/.github/workflows/test-library.yml b/.github/workflows/test-library.yml index eba95662..b4df6e41 100644 --- a/.github/workflows/test-library.yml +++ b/.github/workflows/test-library.yml @@ -8,10 +8,24 @@ on: - 'main' - 'release' paths-ignore: - - '.github/workflows/**' + - '.github/**' + - 'examples/**' + - 'meraki/aio/**' + - 'notebooks/**' + - 'README.md' + - 'LICENSE' + - 'setup.py' pull_request: branches: - 'main' + paths-ignore: + - '.github/**' + - 'examples/**' + - 'meraki/aio/**' + - 'notebooks/**' + - 'README.md' + - 'LICENSE' + - 'setup.py' workflow_dispatch: @@ -22,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 @@ -44,3 +58,4 @@ jobs: - name: Test with pytest run: | pytest --apikey ${{ secrets.TEST_ORG_API_KEY }} --o ${{ secrets.TEST_ORG_ID }} + diff --git a/README.md b/README.md index 34d50e7a..1c0dd83b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Meraki Dashboard API Python Library -The Meraki Dashboard API Python library provides all current Meraki [dashboard API](https://developer.cisco.com/meraki/api-v1/) calls to interface with the Cisco Meraki cloud-managed platform. Meraki generates the library based on dashboard API's OpenAPI spec to keep it up to date with the latest API releases, and provides the full source code for the library including the tools used to generate the library, if you are participating in the Early Access program or would like to contribute to the development of the library. Meraki welcomes constructive pull requests that maintain backwards compatibility with prior versions. The library requires Python 3.7+, receives support from the community, and you can install it via [PyPI](https://pypi.org/project/meraki/): +The Meraki Dashboard API Python library provides all current Meraki [dashboard API](https://developer.cisco.com/meraki/api-v1/) calls to interface with the Cisco Meraki cloud-managed platform. Meraki generates the library based on dashboard API's OpenAPI spec to keep it up to date with the latest API releases, and provides the full source code for the library including the tools used to generate the library, if you are participating in the Early Access program or would like to contribute to the development of the library. Meraki welcomes constructive pull requests that maintain backwards compatibility with prior versions. The library requires Python 3.8+, receives support from the community, and you can install it via [PyPI](https://pypi.org/project/meraki/): pip install --upgrade meraki @@ -41,7 +41,7 @@ While you can make direct HTTP requests to dashboard API in any programming lang 1. Export your API key as an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example: ```shell - export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73 + export MERAKI_DASHBOARD_API_KEY=YOUR_KEY_HERE ``` 2. Alternatively, define your API key as a variable in your source code; this method is not recommended due to its inherent insecurity. @@ -79,7 +79,7 @@ The usage is similiar to the sequential version above. However it has has some d 1. Export your API key as an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example: ```shell - export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73 + export MERAKI_DASHBOARD_API_KEY=YOUR_KEY_HERE ``` 2. Alternatively, define your API key as a variable in your source code; this method is not recommended due to its inherent insecurity. @@ -107,11 +107,8 @@ The usage is similiar to the sequential version above. However it has has some d import asyncio if __name__ == "__main__": - loop = asyncio.get_event_loop() - loop.run_until_complete(my_async_entry_point()) - - # if you are using Python 3.7+ you can also simply - # use the following line instead of the two lines above + + # replace my_async_entry_point with the name of your entry point method asyncio.run(my_async_entry_point()) ``` diff --git a/meraki/__init__.py b/meraki/__init__.py index a3599e70..e734ca8c 100644 --- a/meraki/__init__.py +++ b/meraki/__init__.py @@ -1,4 +1,3 @@ -from datetime import datetime import logging import os @@ -30,6 +29,7 @@ WAIT_ON_RATE_LIMIT, NGINX_429_RETRY_WAIT_TIME, ACTION_BATCH_RETRY_WAIT_TIME, + NETWORK_DELETE_RETRY_WAIT_TIME, RETRY_4XX_ERROR, RETRY_4XX_ERROR_WAIT_TIME, MAXIMUM_RETRIES, diff --git a/meraki/aio/rest_session.py b/meraki/aio/rest_session.py index 74465a99..84f1ed28 100644 --- a/meraki/aio/rest_session.py +++ b/meraki/aio/rest_session.py @@ -1,20 +1,17 @@ -import logging -import sys -from datetime import datetime -import json -import platform import random -import urllib.parse +import time import asyncio +import json +import sys import ssl -import time - +import urllib.parse +from datetime import datetime import aiohttp from meraki.__init__ import __version__ +from meraki.common import * from meraki.config import * -from meraki.exceptions import * from meraki.rest_session import user_agent_extended @@ -66,18 +63,7 @@ def __init__( self.use_iterator_for_get_pages = use_iterator_for_get_pages # Check minimum Python version - version_warning_string = f'This library requires Python 3.7 at minimum. Python versions 3.6 and below ' \ - f'are end of life and end of support per the Python maintainers, and your ' \ - f'interpreter version details are: \n' \ - f'platform.python_version_tuple()[0] = {platform.python_version_tuple()[0]}\n' \ - f'platform.python_version_tuple()[1] = {platform.python_version_tuple()[1]}\n' \ - f'platform.python_version is {platform.python_version()}\n' \ - f'Please consult the readme at your convenience: ' \ - f'https://github.com/meraki/dashboard-api-python' - if int(platform.python_version_tuple()[0]) != 3: - sys.exit(version_warning_string) - elif int(platform.python_version_tuple()[1]) < 7: - sys.exit(version_warning_string) + check_python_version() # Check base URL if "v0" in self._base_url: diff --git a/meraki/common.py b/meraki/common.py new file mode 100644 index 00000000..1dd3c8de --- /dev/null +++ b/meraki/common.py @@ -0,0 +1,19 @@ +import platform +from meraki.exceptions import * + + +def check_python_version(): + # Check minimum Python version + + if not (int(platform.python_version_tuple()[0]) == 3 and int(platform.python_version_tuple()[1]) >= 8): + message = ( + f"This library requires Python 3.8 at minimum. Python versions 3.7 and below " + f"no longer receive security updates since reaching end of life and of support " + f"per the Python maintainers. Your interpreter version is: {platform.python_version()}. " + f"Please consult the readme at your convenience: https://github.com/meraki/dashboard-api-python " + f"Additional details: " + f"python_version_tuple()[0] = {platform.python_version_tuple()[0]}; " + f"python_version_tuple()[1] = {platform.python_version_tuple()[1]} " + ) + + raise PythonVersionError(message) diff --git a/meraki/exceptions.py b/meraki/exceptions.py index 7fa830b5..55475dda 100644 --- a/meraki/exceptions.py +++ b/meraki/exceptions.py @@ -13,13 +13,17 @@ class APIResponseError(Exception): Exception class raised from HTTP class methods. Used as a single catch-all error for any possible requests exception error that might happen during communication with Meraki API to simplify caller coding. """ + def __init__(self, obj_name, status_code, error_msg): self.obj_name = obj_name self.status_code = status_code self.reason = error_msg def exc_message(self): - return f'HTTP call within object "{self.obj_name}" failed. Status code is "{self.status_code}". Error message is: "{self.reason}".' + return ( + f'HTTP call within object "{self.obj_name}" failed. Status code is "{self.status_code}". ' + f'Error message is: "{self.reason}".' + ) def json(self): return dict(error=self.reason, status_code=self.status_code) @@ -94,3 +98,13 @@ def __init__(self, metadata, response, message): def __repr__(self): return f"{self.tag}, {self.operation} - {self.status} {self.reason}, {self.message}" + + +class PythonVersionError(Exception): + """Exception raised for unsupported Python versions.""" + + def __init__(self, message): + + self.message = message + + super().__init__(self.message) diff --git a/meraki/rest_session.py b/meraki/rest_session.py index cce98284..2f4e5a5c 100644 --- a/meraki/rest_session.py +++ b/meraki/rest_session.py @@ -1,16 +1,15 @@ -import sys -from datetime import datetime -import json -import platform import random -import urllib.parse import time - import requests +import json +import sys + +import urllib.parse +from datetime import datetime -from meraki.config import * -from meraki.exceptions import * from meraki.__init__ import __version__ +from meraki.common import * +from meraki.config import * def user_agent_extended(be_geo_id, caller): @@ -55,22 +54,6 @@ def user_agent_extended(be_geo_id, caller): # Main module interface -def check_python_version(): - # Check minimum Python version - version_warning_string = f'This library requires Python 3.7 at minimum. Python versions 3.6 and below ' \ - f'are end of life and end of support per the Python maintainers, and your ' \ - f'interpreter version details are: \n' \ - f'platform.python_version_tuple()[0] = {platform.python_version_tuple()[0]}\n' \ - f'platform.python_version_tuple()[1] = {platform.python_version_tuple()[1]}\n' \ - f'platform.python_version is {platform.python_version()}\n' \ - f'Please consult the readme at your convenience: ' \ - f'https://github.com/meraki/dashboard-api-python' - if int(platform.python_version_tuple()[0]) != 3: - sys.exit(version_warning_string) - elif int(platform.python_version_tuple()[1]) < 7: - sys.exit(version_warning_string) - - class RestSession(object): def __init__( self, @@ -207,7 +190,8 @@ def request(self, metadata, method, url, **kwargs): retries -= 1 if retries == 0: if e.response and e.response.status_code: - raise APIError(metadata, APIResponseError(e.__class__.__name__, e.response.status_code, str(e))) + raise APIError(metadata, APIResponseError(e.__class__.__name__, + e.response.status_code, str(e))) else: raise APIError(metadata, APIResponseError(e.__class__.__name__, 503, str(e))) else: From ca48541625ac773dc10da6835aa2554c373e515e Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Mon, 9 Oct 2023 18:17:25 -0700 Subject: [PATCH 02/16] Removed Python 3.12 from test suite The AIO library uses aiohttp which does not have a production-ready wheel for Python 3.12 yet. We should reconsider 3.12 support once a production-ready wheel exists for aiohttp in Python 3.12. Updated requirements to take advantage of security fixes in dependent libraries. --- .github/workflows/test-library.yml | 2 +- requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-library.yml b/.github/workflows/test-library.yml index b4df6e41..996dcf9c 100644 --- a/.github/workflows/test-library.yml +++ b/.github/workflows/test-library.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 diff --git a/requirements.txt b/requirements.txt index 465c005e..88bfff37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -aiohttp~=3.8.3 -requests~=2.28.1 -meraki~=1.27.0 +aiohttp~=3.8.6 +requests~=2.31.0 +meraki~=1.38.0 setuptools~=67.2.0 Jinja2==3.1.2 pytest~=7.2.1 \ No newline at end of file From 4fe03a72bfd3b1c3f813a3695082e8f81836c0c1 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Thu, 12 Oct 2023 10:17:17 -0700 Subject: [PATCH 03/16] Generator improvements * Minor improvements for library and snippets generators --- generator/generate_library.py | 23 ++++++++++++----------- generator/generate_snippets.py | 8 +++++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/generator/generate_library.py b/generator/generate_library.py index e740bdf3..25a7fa84 100644 --- a/generator/generate_library.py +++ b/generator/generate_library.py @@ -46,16 +46,17 @@ def generate_pagination_parameters(operation: str): def check_python_version(): # Check minimum Python version - version_warning_string = f'The generator requires Python 3.10 at minimum. ' \ - f'Your interpreter version details are: \n' \ - f'platform.python_version_tuple()[0] = {platform.python_version_tuple()[0]}\n' \ - f'platform.python_version_tuple()[1] = {platform.python_version_tuple()[1]}\n' \ - f'platform.python_version is {platform.python_version()}\n' \ - f'Please consult the generator readme at your convenience: ' \ - f'https://github.com/meraki/dashboard-api-python/blob/main/generator/readme.md' - if int(platform.python_version_tuple()[0]) != 3: - sys.exit(version_warning_string) - elif int(platform.python_version_tuple()[1]) < 7: + version_warning_string = ( + f"This library generator requires Python 3.10 at minimum. " + f"Your interpreter version is: {platform.python_version()}. " + f"Please consult the readme at your convenience: " + f"https://github.com/meraki/dashboard-api-python/blob/main/generator/readme.md " + f"Additional details: " + f"python_version_tuple()[0] = {platform.python_version_tuple()[0]}; " + f"python_version_tuple()[1] = {platform.python_version_tuple()[1]} " + ) + + if not (int(platform.python_version_tuple()[0]) == 3 and int(platform.python_version_tuple()[1]) >= 10): sys.exit(version_warning_string) @@ -223,7 +224,7 @@ def generate_library(spec: dict, version_number: str, is_github_action: bool): os.mkdir(directory) # Files that are not generated - non_generated = ['__init__.py', 'config.py', 'exceptions.py', 'rest_session.py', 'api/__init__.py', + non_generated = ['__init__.py', 'config.py', 'common.py', 'exceptions.py', 'rest_session.py', 'api/__init__.py', 'aio/__init__.py', 'aio/rest_session.py', 'aio/api/__init__.py', 'api/batch/__init__.py'] base_url = 'https://raw.githubusercontent.com/meraki/dashboard-api-python/master/meraki/' for file in non_generated: diff --git a/generator/generate_snippets.py b/generator/generate_snippets.py index 0f8b6aca..3989e679 100644 --- a/generator/generate_snippets.py +++ b/generator/generate_snippets.py @@ -6,11 +6,13 @@ CALL_TEMPLATE = Template('''import meraki -# Defining your API key as a variable in source code is not recommended -API_KEY = '6bec40cf957de430a6f1f2baa056b99a4fac9ea0' -# Instead, use an environment variable as shown under the Usage section +# Defining your API key as a variable in source code is discouraged. +# This API key is for a read-only docs-specific environment. +# In your own code, use an environment variable as shown under the Usage section # @ https://github.com/meraki/dashboard-api-python/ +API_KEY = '75dd5334bef4d2bc96f26138c163c0a3fa0b5ca6' + dashboard = meraki.DashboardAPI(API_KEY) {{ parameter_assignments }} response = dashboard.{{ section }}.{{ operation }}({{ parameters }}) From 7dd5f57c80636e1e0a04c48c32a88c840fc0a337 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Thu, 12 Oct 2023 11:44:22 -0700 Subject: [PATCH 04/16] Update test_dashboard_api_python_library.py --- tests/test_dashboard_api_python_library.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dashboard_api_python_library.py b/tests/test_dashboard_api_python_library.py index 63de223d..cdd0158b 100644 --- a/tests/test_dashboard_api_python_library.py +++ b/tests/test_dashboard_api_python_library.py @@ -13,7 +13,7 @@ def api_key(pytestconfig): @pytest.fixture(scope='session') def dashboard(api_key): - return meraki.DashboardAPI(api_key, suppress_logging=True) + return meraki.DashboardAPI(api_key, suppress_logging=True, network_delete_retry_wait_time=600) @pytest.fixture(scope='session') From 9d724f302a3bad0e9fdd239e237435eac41db8a7 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Mon, 16 Oct 2023 15:21:19 -0700 Subject: [PATCH 05/16] Bugfix for AIO library Fix for #229. Thank you @ollipa for reporting this. --- meraki/aio/rest_session.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/meraki/aio/rest_session.py b/meraki/aio/rest_session.py index 84f1ed28..c597b5b1 100644 --- a/meraki/aio/rest_session.py +++ b/meraki/aio/rest_session.py @@ -240,7 +240,10 @@ async def _request(self, metadata, method, url, **kwargs): else: try: message = await response.json(content_type = None) - message_is_dict = True + if isinstance(message, dict): + message_is_dict = True + else: + message_is_dict = False except aiohttp.client_exceptions.ContentTypeError: message_is_dict = False try: From ae5448150e9b2018414b72456ca08b46aef09de5 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Tue, 17 Oct 2023 09:40:01 -0700 Subject: [PATCH 06/16] Update README.md Updating async readme for #226. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 1c0dd83b..da65dd3a 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,15 @@ You can find fully working example scripts in the **examples** folder. ## AsyncIO **asyncio** is a library to write concurrent code using the **async/await** syntax. Special thanks to Heimo Stieg ([@coreGreenberet](https://github.com/coreGreenberet)) who has ported the API to asyncio. +### Installation on macOS + +If you use a Mac, then you may need to take +[additional Python installation steps](https://bugs.python.org/issue43404) that aren't required on other platforms. This +is [a limitation of macOS and not the library](https://github.com/meraki/dashboard-api-python/issues/226). This step is +not required on Windows. + +### Usage + The usage is similiar to the sequential version above. However it has has some differences. 1. Export your API key as an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example: From 7e587ea5fbcc826b7346975367fa2e548d80bb77 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Tue, 24 Oct 2023 16:17:59 -0700 Subject: [PATCH 07/16] Black linting updates Linting update (Black) --- README.md | 115 +++++--- generator/generate_library.py | 2 +- generator/generate_snippets.py | 314 ++++++++++++--------- generator/readme.md | 25 +- meraki/__init__.py | 14 +- meraki/aio/__init__.py | 4 +- meraki/aio/rest_session.py | 8 +- meraki/common.py | 5 +- meraki/config.py | 16 +- meraki/exceptions.py | 1 - meraki/rest_session.py | 8 +- tests/test_dashboard_api_python_library.py | 7 +- 12 files changed, 308 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index da65dd3a..c015976d 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,76 @@ # Meraki Dashboard API Python Library -The Meraki Dashboard API Python library provides all current Meraki [dashboard API](https://developer.cisco.com/meraki/api-v1/) calls to interface with the Cisco Meraki cloud-managed platform. Meraki generates the library based on dashboard API's OpenAPI spec to keep it up to date with the latest API releases, and provides the full source code for the library including the tools used to generate the library, if you are participating in the Early Access program or would like to contribute to the development of the library. Meraki welcomes constructive pull requests that maintain backwards compatibility with prior versions. The library requires Python 3.8+, receives support from the community, and you can install it via [PyPI](https://pypi.org/project/meraki/): +The Meraki Dashboard API Python library provides all current +Meraki [dashboard API](https://developer.cisco.com/meraki/api-v1/) calls to interface with the Cisco Meraki +cloud-managed platform. Meraki generates the library based on dashboard API's OpenAPI spec to keep it up to date with +the latest API releases, and provides the full source code for the library including the tools used to generate the +library, if you are participating in the Early Access program or would like to contribute to the development of the +library. Meraki welcomes constructive pull requests that maintain backwards compatibility with prior versions. The +library requires Python 3.8+, receives support from the community, and you can install it +via [PyPI](https://pypi.org/project/meraki/): pip install --upgrade meraki -If you participate in [our Early Access program](https://community.meraki.com/t5/Developers-APIs/UPDATED-Beta-testing-with-the-Meraki-Developer-Early-Access/m-p/145344#M5808) and would like to use early access features via the library, you'll [find instructions in the generator readme](https://github.com/meraki/dashboard-api-python/tree/main/generator#readme). +If you participate +in [our Early Access program](https://community.meraki.com/t5/Developers-APIs/UPDATED-Beta-testing-with-the-Meraki-Developer-Early-Access/m-p/145344#M5808) +and would like to use early access features via the library, +you'll [find instructions in the generator readme](https://github.com/meraki/dashboard-api-python/tree/main/generator#readme). ## Features -While you can make direct HTTP requests to dashboard API in any programming language or REST API client, using a client library can make it easier for you to focus on your specific use case, without the overhead of having to write functions to handle the dashboard API calls. The Python library can also take care of error handling, logging, retries, and other convenient processes and options for you automatically. +While you can make direct HTTP requests to dashboard API in any programming language or REST API client, using a client +library can make it easier for you to focus on your specific use case, without the overhead of having to write functions +to handle the dashboard API calls. The Python library can also take care of error handling, logging, retries, and other +convenient processes and options for you automatically. -* Support for all API endpoints, as it uses the [OpenAPI specification](https://api.meraki.com/api/v1/openapiSpec) to generate source code +* Support for all API endpoints, as it uses the [OpenAPI specification](https://api.meraki.com/api/v1/openapiSpec) to + generate source code * Log all API requests made to a local file as well as on-screen console -* Automatic retries upon 429 rate limit errors, using the [`Retry-After` field](https://developer.cisco.com/meraki/api-v1/#!rate-limit) within response headers +* Automatic retries upon 429 rate limit errors, using + the [`Retry-After` field](https://developer.cisco.com/meraki/api-v1/#!rate-limit) within response headers * Get all (or a specified number of) pages of data with built-in pagination control * Tweak settings such as maximum retries, certificate path, suppress logging, and other options * Simulate POST/PUT/DELETE calls to preview first, so that network configuration does not get changed ## Setup -1. Enable API access in your Meraki dashboard organization and obtain an API key ([instructions](https://documentation.meraki.com/zGeneral_Administration/Other_Topics/The_Cisco_Meraki_Dashboard_API)) +1. Enable API access in your Meraki dashboard organization and obtain an API + key ([instructions](https://documentation.meraki.com/zGeneral_Administration/Other_Topics/The_Cisco_Meraki_Dashboard_API)) -2. Keep your API key safe and secure, as it is similar to a password for your dashboard. If publishing your Python code to a wider audience, please research secure handling of API keys. +2. Keep your API key safe and secure, as it is similar to a password for your dashboard. If publishing your Python code + to a wider audience, please research secure handling of API keys. 3. Install the latest version of [Python 3](ttps://wiki.python.org/moin/BeginnersGuide/NonProgrammers) -4. Use _pip_ (or an alternative such as _easy_install_) to install the library from the Python [Package Index](https://pypi.org/project/meraki/): +4. Use _pip_ (or an alternative such as _easy_install_) to install the library from the + Python [Package Index](https://pypi.org/project/meraki/): * `pip install meraki` - * If you have both Python3 and Python2 installed, you may need to use `pip3` (so `pip3 install meraki`) along with `python3` on your system - * If _meraki_ was previously installed, you can upgrade to the latest non-beta release with `pip install --upgrade meraki` - -5. The library supports Meraki dashboard API v1. You can also specify the version of the library when installing with _pip_: - * See the full [release history](https://pypi.org/project/meraki/#history) to pick the version you want, or use `pip install meraki==` without including a version number to display the list of available versions + * If you have both Python3 and Python2 installed, you may need to use `pip3` (so `pip3 install meraki`) along + with `python3` on your system + * If _meraki_ was previously installed, you can upgrade to the latest non-beta release + with `pip install --upgrade meraki` + +5. The library supports Meraki dashboard API v1. You can also specify the version of the library when installing with + _pip_: + * See the full [release history](https://pypi.org/project/meraki/#history) to pick the version you want, or + use `pip install meraki==` without including a version number to display the list of available versions * Versions begin with _1_ (1.0.0b**z** for beta) * Specify the version you want with the install command; for example: `pip install meraki==1.34.0` * You can also see the version currently installed with `pip show meraki` - * End-of-life v0 versions of the Python library begin with _0_ (0.**x**.**y**) and are not supported nor recommended. + * End-of-life v0 versions of the Python library begin with _0_ (0.**x**.**y**) and are not supported nor + recommended. ## Usage -1. Export your API key as an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example: + +1. Export your API key as + an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example: ```shell export MERAKI_DASHBOARD_API_KEY=YOUR_KEY_HERE ``` -2. Alternatively, define your API key as a variable in your source code; this method is not recommended due to its inherent insecurity. +2. Alternatively, define your API key as a variable in your source code; this method is not recommended due to its + inherent insecurity. 3. Single line of code to import and use the library goes at the top of your script: @@ -58,40 +84,48 @@ While you can make direct HTTP requests to dashboard API in any programming lang dashboard = meraki.DashboardAPI() ``` -5. Make dashboard API calls in your source code, using the format _client.scope.operation_, where _client_ is the name you defined in the previous step (**dashboard** above), _scope_ is the corresponding scope that represents the first tag from the OpenAPI spec, and _operation_ is the operation of the API endpoint. For example, to make a call to get the list of organizations accessible by the API key defined in step 1, use this function call: +5. Make dashboard API calls in your source code, using the format _client.scope.operation_, where _client_ is the name + you defined in the previous step (**dashboard** above), _scope_ is the corresponding scope that represents the first + tag from the OpenAPI spec, and _operation_ is the operation of the API endpoint. For example, to make a call to get + the list of organizations accessible by the API key defined in step 1, use this function call: ```python my_orgs = dashboard.organizations.getOrganizations() ``` ### Examples + You can find fully working example scripts in the **examples** folder. -| Script | Purpose | -|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Script | Purpose | +|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **org_wide_clients.py** | That code collects the clients of all networks, in all orgs to which the key has access. No changes are made, since only GET endpoints are called, and the data is written to local CSV output files. | ## AsyncIO -**asyncio** is a library to write concurrent code using the **async/await** syntax. Special thanks to Heimo Stieg ([@coreGreenberet](https://github.com/coreGreenberet)) who has ported the API to asyncio. + +**asyncio** is a library to write concurrent code using the **async/await** syntax. Special thanks to Heimo +Stieg ([@coreGreenberet](https://github.com/coreGreenberet)) who has ported the API to asyncio. ### Installation on macOS -If you use a Mac, then you may need to take +If you use a Mac, then you may need to take [additional Python installation steps](https://bugs.python.org/issue43404) that aren't required on other platforms. This -is [a limitation of macOS and not the library](https://github.com/meraki/dashboard-api-python/issues/226). This step is +is [a limitation of macOS and not the library](https://github.com/meraki/dashboard-api-python/issues/226). This step is not required on Windows. ### Usage The usage is similiar to the sequential version above. However it has has some differences. -1. Export your API key as an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example: +1. Export your API key as + an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example: ```shell export MERAKI_DASHBOARD_API_KEY=YOUR_KEY_HERE ``` -2. Alternatively, define your API key as a variable in your source code; this method is not recommended due to its inherent insecurity. +2. Alternatively, define your API key as a variable in your source code; this method is not recommended due to its + inherent insecurity. 3. Single line of code to import and use the library goes at the top of your script: @@ -104,41 +138,52 @@ The usage is similiar to the sequential version above. However it has has some d ```python async with meraki.aio.AsyncDashboardAPI() as aiomeraki: ``` - The **async with** statement is important here to make sure, that the client sessions will be closed after using the api. + The **async with** statement is important here to make sure, that the client sessions will be closed after using the + api. -5. Make dashboard API calls in your source code, using the format await _client.section.operation_, where _client_ is the name you defined in the previous step (**aiomeraki** above), _section_ is the corresponding group (or tag from the OpenAPI spec) from the [API docs](https://developer.cisco.com/meraki/api/#/rest), and _operation_ is the name (or operation ID from OpenAPI) of the API endpoint. For example, to make a call to get the list of organizations accessible by the API key defined in step 1, use this function call: +5. Make dashboard API calls in your source code, using the format await _client.section.operation_, where _client_ is + the name you defined in the previous step (**aiomeraki** above), _section_ is the corresponding group (or tag from + the OpenAPI spec) from the [API docs](https://developer.cisco.com/meraki/api/#/rest), and _operation_ is the name (or + operation ID from OpenAPI) of the API endpoint. For example, to make a call to get the list of organizations + accessible by the API key defined in step 1, use this function call: ```python my_orgs = await aiomeraki.organizations.getOrganizations() ``` 6. Run everything inside an event loop. + ```python import asyncio if __name__ == "__main__": - # replace my_async_entry_point with the name of your entry point method asyncio.run(my_async_entry_point()) ``` - ### Examples + You can find fully working example scripts in the **examples** folder. -| Script | Purpose | +| Script | Purpose | |-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **aio_org_wide_clients.py** | That code is a asyncio port from org_wide_clients.py and collects the clients of all networks, in all orgs to which the key has access. No changes are made, since only GET endpoints are called, and the data is written to local CSV output files. | -| **aio_ips2firewall.py** | That code will collect the source IP of security events and creates L7 firewall rules to block them. `usage: aio_ips2firewall.py [-h] -o ORGANIZATIONS [ORGANIZATIONS ...] [-f FILTER] [-s] [-d DAYS]` | - +| **aio_org_wide_clients.py** | That code is a asyncio port from org_wide_clients.py and collects the clients of all +networks, in all orgs to which the key has access. No changes are made, since only GET endpoints are called, and the +data is written to local CSV output files. | +| **aio_ips2firewall.py** | That code will collect the source IP of security events and creates L7 firewall rules to +block them. `usage: aio_ips2firewall.py [-h] -o ORGANIZATIONS [ORGANIZATIONS ...] [-f FILTER] [-s] [-d DAYS]` | ## Note for application developers and ecosystem partners -We're so glad that you're leveraging our Python library. It's best practice to identify your application with every API request that you make. You can easily do this automatically just by following the format defined in [config.py](https://github.com/meraki/dashboard-api-python/blob/master/meraki/config.py) and passing the session kwarg: +We're so glad that you're leveraging our Python library. It's best practice to identify your application with every API +request that you make. You can easily do this automatically just by following the format defined +in [config.py](https://github.com/meraki/dashboard-api-python/blob/master/meraki/config.py) and passing the session +kwarg: ``` Python MERAKI_PYTHON_SDK_CALLER ``` -Unless you are an ecosystem partner, this identifier is optional. +Unless you are an ecosystem partner, this identifier is optional. -1. If you are an ecosystem partner and you have questions about this requirement, please reach out to your ecosystem rep. +1. If you are an ecosystem partner and you have questions about this requirement, please reach out to your ecosystem + rep. 2. If you have any questions about the formatting, please ask your question by opening an issue in this repo. diff --git a/generator/generate_library.py b/generator/generate_library.py index 25a7fa84..7c035d30 100644 --- a/generator/generate_library.py +++ b/generator/generate_library.py @@ -1,7 +1,7 @@ import getopt import os -import sys import platform +import sys import jinja2 import requests diff --git a/generator/generate_snippets.py b/generator/generate_snippets.py index 3989e679..4b0b2298 100644 --- a/generator/generate_snippets.py +++ b/generator/generate_snippets.py @@ -4,7 +4,8 @@ import requests from jinja2 import Template -CALL_TEMPLATE = Template('''import meraki +CALL_TEMPLATE = Template( + """import meraki # Defining your API key as a variable in source code is discouraged. # This API key is for a read-only docs-specific environment. @@ -18,37 +19,38 @@ response = dashboard.{{ section }}.{{ operation }}({{ parameters }}) print(response) -''') +""" +) -REVERSE_PAGINATION = ['getNetworkEvents', 'getOrganizationConfigurationChanges'] +REVERSE_PAGINATION = ["getNetworkEvents", "getOrganizationConfigurationChanges"] # Helper function to convert camel case parameter name to snake case def snakify(param): - ret = '' + ret = "" for s in param: if s.islower(): ret += s - elif s == '_': - ret += '_' + elif s == "_": + ret += "_" else: - ret += '_' + s.lower() + ret += "_" + s.lower() return ret # Helper function to return pagination parameters depending on endpoint def generate_pagination_parameters(operation): ret = { - 'total_pages': { - 'type': 'integer or string', - 'description': 'total number of pages to retrieve, -1 or "all" for all pages', + "total_pages": { + "type": "integer or string", + "description": 'total number of pages to retrieve, -1 or "all" for all pages', }, - 'direction': { - 'type': 'string', - 'description': 'direction to paginate, either "next" or "prev" (default) page' + "direction": { + "type": "string", + "description": 'direction to paginate, either "next" or "prev" (default) page' if operation in REVERSE_PAGINATION else 'direction to paginate, either "next" (default) or "prev" page', - } + }, } return ret @@ -61,41 +63,41 @@ def parse_params(operation, parameters, param_filters=[]): # Create dict with information on endpoint's parameters params = {} for p in parameters: - name = p['name'] - if 'schema' in p: - keys = p['schema']['properties'] + name = p["name"] + if "schema" in p: + keys = p["schema"]["properties"] for k in keys: - if 'required' in p['schema'] and k in p['schema']['required']: - params[k] = {'required': True} + if "required" in p["schema"] and k in p["schema"]["required"]: + params[k] = {"required": True} else: - params[k] = {'required': False} - params[k]['in'] = p['in'] - params[k]['type'] = keys[k]['type'] - params[k]['description'] = keys[k]['description'] - if 'enum' in keys[k]: - params[k]['enum'] = keys[k]['enum'] - if 'example' in p['schema'] and k in p['schema']['example']: - params[k]['example'] = p['schema']['example'][k] - elif 'required' in p and p['required']: - params[name] = {'required': True} - params[name]['in'] = p['in'] - params[name]['type'] = p['type'] - if 'description' in p: - params[name]['description'] = p['description'] + params[k] = {"required": False} + params[k]["in"] = p["in"] + params[k]["type"] = keys[k]["type"] + params[k]["description"] = keys[k]["description"] + if "enum" in keys[k]: + params[k]["enum"] = keys[k]["enum"] + if "example" in p["schema"] and k in p["schema"]["example"]: + params[k]["example"] = p["schema"]["example"][k] + elif "required" in p and p["required"]: + params[name] = {"required": True} + params[name]["in"] = p["in"] + params[name]["type"] = p["type"] + if "description" in p: + params[name]["description"] = p["description"] else: - params[name]['description'] = '(required)' - if 'enum' in p: - params[name]['enum'] = p['enum'] + params[name]["description"] = "(required)" + if "enum" in p: + params[name]["enum"] = p["enum"] else: - params[name] = {'required': False} - params[name]['in'] = p['in'] - params[name]['type'] = p['type'] - params[name]['description'] = p['description'] - if 'enum' in p: - params[name]['enum'] = p['enum'] + params[name] = {"required": False} + params[name]["in"] = p["in"] + params[name]["type"] = p["type"] + params[name]["description"] = p["description"] + if "enum" in p: + params[name]["enum"] = p["enum"] # Add custom library parameters to handle pagination - if 'perPage' in params: + if "perPage" in params: params.update(generate_pagination_parameters(operation)) # Return parameters based on matching input filters @@ -103,77 +105,109 @@ def parse_params(operation, parameters, param_filters=[]): return params else: ret = {} - if 'required' in param_filters: - ret.update({k: v for k, v in params.items() if 'required' in v and v['required']}) - if 'pagination' in param_filters: - ret.update(generate_pagination_parameters(operation) if 'perPage' in params else {}) - if 'optional' in param_filters: - ret.update({k: v for k, v in params.items() if 'required' in v and not v['required']}) - if 'path' in param_filters: - ret.update({k: v for k, v in params.items() if 'in' in v and v['in'] == 'path'}) - if 'query' in param_filters: - ret.update({k: v for k, v in params.items() if 'in' in v and v['in'] == 'query'}) - if 'body' in param_filters: - ret.update({k: v for k, v in params.items() if 'in' in v and v['in'] == 'body'}) - if 'array' in param_filters: - ret.update({k: v for k, v in params.items() if 'in' in v and v['type'] == 'array'}) - if 'enum' in param_filters: - ret.update({k: v for k, v in params.items() if 'enum' in v}) + if "required" in param_filters: + ret.update( + {k: v for k, v in params.items() if "required" in v and v["required"]} + ) + if "pagination" in param_filters: + ret.update( + generate_pagination_parameters(operation) if "perPage" in params else {} + ) + if "optional" in param_filters: + ret.update( + { + k: v + for k, v in params.items() + if "required" in v and not v["required"] + } + ) + if "path" in param_filters: + ret.update( + {k: v for k, v in params.items() if "in" in v and v["in"] == "path"} + ) + if "query" in param_filters: + ret.update( + {k: v for k, v in params.items() if "in" in v and v["in"] == "query"} + ) + if "body" in param_filters: + ret.update( + {k: v for k, v in params.items() if "in" in v and v["in"] == "body"} + ) + if "array" in param_filters: + ret.update( + {k: v for k, v in params.items() if "in" in v and v["type"] == "array"} + ) + if "enum" in param_filters: + ret.update({k: v for k, v in params.items() if "enum" in v}) return ret # Generate text for parameter assignments def process_assignments(parameters): - text = '\n' + text = "\n" for k, v in parameters.items(): param_name = snakify(k) - if param_name == 'id': - param_name = 'id_' - - if v == 'list': - text += f'{param_name} = []\n' - elif v == 'float': - text += f'{param_name} = 0.0\n' - elif v == 'int': - text += f'{param_name} = 0\n' - elif v == 'bool': - text += f'{param_name} = False\n' - elif v == 'dict': - text += f'{param_name} = {{}}\n' - elif v == 'str': - text += f'{param_name} = \'\'\n' + if param_name == "id": + param_name = "id_" + + if v == "list": + text += f"{param_name} = []\n" + elif v == "float": + text += f"{param_name} = 0.0\n" + elif v == "int": + text += f"{param_name} = 0\n" + elif v == "bool": + text += f"{param_name} = False\n" + elif v == "dict": + text += f"{param_name} = {{}}\n" + elif v == "str": + text += f"{param_name} = ''\n" else: if type(v) == str: - value = f'\'{v}\'' + value = f"'{v}'" else: value = v - text += f'{param_name} = {value}\n' + text += f"{param_name} = {value}\n" return text def main(): # Get latest OpenAPI specification - spec = requests.get('https://api.meraki.com/api/v1/openapiSpec').json() + spec = requests.get("https://api.meraki.com/api/v1/openapiSpec").json() # Supported scopes list will include organizations, networks, devices, and all product types. - supported_scopes = ['administered', 'organizations', 'networks', 'devices', 'appliance', 'camera', 'cellularGateway', 'insight', - 'sm', 'switch', 'wireless', 'sensor', 'licensing', 'secureConnect'] + supported_scopes = [ + "administered", + "organizations", + "networks", + "devices", + "appliance", + "camera", + "cellularGateway", + "insight", + "sm", + "switch", + "wireless", + "sensor", + "licensing", + "secureConnect", + ] # legacy scopes = ['organizations', 'networks', 'devices', # 'appliance', 'camera', 'cellularGateway', 'insight', 'sm', 'switch', 'wireless'] - tags = spec['tags'] - paths = spec['paths'] + tags = spec["tags"] + paths = spec["paths"] # Scopes used when generating the library will depend on the provided version of the API spec. - scopes = {tag['name']: {} for tag in tags if tag['name'] in supported_scopes} + scopes = {tag["name"]: {} for tag in tags if tag["name"] in supported_scopes} # Organize data operations = [] for path, methods in paths.items(): for method in methods: endpoint = paths[path][method] - tags = endpoint['tags'] - operation = endpoint['operationId'] + tags = endpoint["tags"] + operation = endpoint["operationId"] operations.append(operation) scope = tags[0] if path not in scopes[scope]: @@ -183,91 +217,99 @@ def main(): # Generate API libraries for scope in scopes: - print(f'...generating {scope}') + print(f"...generating {scope}") section = scopes[scope] for path, methods in section.items(): for method, endpoint in methods.items(): # Get metadata - tags = endpoint['tags'] - operation = endpoint['operationId'] - description = endpoint['summary'] - parameters = endpoint['parameters'] if 'parameters' in endpoint else None - responses = endpoint['responses'] # not actually used here for library generation + tags = endpoint["tags"] + operation = endpoint["operationId"] + description = endpoint["summary"] + parameters = ( + endpoint["parameters"] if "parameters" in endpoint else None + ) + responses = endpoint[ + "responses" + ] # not actually used here for library generation required = {} optional = {} if parameters: - if 'perPage' in parse_params(operation, parameters): + if "perPage" in parse_params(operation, parameters): pagination = True else: pagination = False - for p, values in parse_params(operation, parameters, 'required').items(): - if 'example' in values: - required[p] = values['example'] - elif p == 'organizationId': - required[p] = '549236' - elif p == 'networkId': + for p, values in parse_params( + operation, parameters, "required" + ).items(): + if "example" in values: + required[p] = values["example"] + elif p == "organizationId": + required[p] = "549236" + elif p == "networkId": # DevNet Sandbox ALWAYS ON network @ https://n149.meraki.com/o/-t35Mb/manage/organization/overview - required[p] = 'L_646829496481105433' - elif p == 'serial': - required[p] = 'Q2QN-9J8L-SLPD' - elif values['type'] == 'array': - required[p] = 'list' - elif values['type'] == 'number': - required[p] = 'float' - elif values['type'] == 'integer': - required[p] = 'int' - elif values['type'] == 'boolean': - required[p] = 'bool' - elif values['type'] == 'object': - required[p] = 'dict' - elif values['type'] == 'string': - required[p] = 'str' + required[p] = "L_646829496481105433" + elif p == "serial": + required[p] = "Q2QN-9J8L-SLPD" + elif values["type"] == "array": + required[p] = "list" + elif values["type"] == "number": + required[p] = "float" + elif values["type"] == "integer": + required[p] = "int" + elif values["type"] == "boolean": + required[p] = "bool" + elif values["type"] == "object": + required[p] = "dict" + elif values["type"] == "string": + required[p] = "str" else: sys.exit(p, values) if pagination: if operation not in REVERSE_PAGINATION: - optional['total_pages'] = 'all' + optional["total_pages"] = "all" else: - optional['total_pages'] = 3 + optional["total_pages"] = 3 - for p, values in parse_params(operation, parameters, 'optional').items(): - if 'example' in values: - optional[p] = values['example'] + for p, values in parse_params( + operation, parameters, "optional" + ).items(): + if "example" in values: + optional[p] = values["example"] - if operation == 'createNetworkGroupPolicy': + if operation == "createNetworkGroupPolicy": print(required) print(optional) - if 'code_snippets' not in os.listdir(): - os.mkdir('code_snippets') + if "code_snippets" not in os.listdir(): + os.mkdir("code_snippets") - with open(f'code_snippets/{operation}.py', 'w', encoding='utf-8') as fp: + with open(f"code_snippets/{operation}.py", "w", encoding="utf-8") as fp: if required.items(): - parameters_text = '\n ' + parameters_text = "\n " for k, v in required.items(): param_name = snakify(k) - if param_name == 'id': - param_name = 'id_' - parameters_text += f'{param_name}, ' + if param_name == "id": + param_name = "id_" + parameters_text += f"{param_name}, " for k, v in optional.items(): - if k == 'total_pages' and v == 'all': - parameters_text += f'total_pages=\'all\'' - elif k == 'total_pages' and v == 1: - parameters_text += f'total_pages=1' + if k == "total_pages" and v == "all": + parameters_text += f"total_pages='all'" + elif k == "total_pages" and v == 1: + parameters_text += f"total_pages=1" elif type(v) == str: - parameters_text += f'\n {k}=\'{v}\', ' + parameters_text += f"\n {k}='{v}', " else: - parameters_text += f'\n {k}={v}, ' - if parameters_text[-2:] == ', ': + parameters_text += f"\n {k}={v}, " + if parameters_text[-2:] == ", ": parameters_text = parameters_text[:-2] - parameters_text += '\n' + parameters_text += "\n" else: - parameters_text = '' + parameters_text = "" fp.write( CALL_TEMPLATE.render( @@ -279,5 +321,5 @@ def main(): ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/generator/readme.md b/generator/readme.md index 84a07ca9..eb2c24ef 100644 --- a/generator/readme.md +++ b/generator/readme.md @@ -1,17 +1,28 @@ # Generating the Meraki Dashboard API Python Library -Generally speaking, you will not need to generate this yourself. Simply use the official PyPI package via `pip install --update meraki`. +Generally speaking, you will not need to generate this yourself. Simply use the official PyPI package +via `pip install --update meraki`. -However, if you participate in Early Access features, you may want to generate a library to match your org's spec. In which case, follow along. +However, if you participate in Early Access features, you may want to generate a library to match your org's spec. In +which case, follow along. > **NB:** The generator requires Python 3.10 or later. 1. Clone this repo locally. 2. Open a terminal in this `generator` folder. -3. *Optional:* If you want to work with beta endpoints, then first [review the warnings, and then opt one of your orgs into the Early API Access program](https://community.meraki.com/t5/Developers-APIs/UPDATED-Beta-testing-with-the-Meraki-Developer-Early-Access/m-p/145344#M5808). +3. *Optional:* If you want to work with beta endpoints, then + first [review the warnings, and then opt one of your orgs into the Early API Access program](https://community.meraki.com/t5/Developers-APIs/UPDATED-Beta-testing-with-the-Meraki-Developer-Early-Access/m-p/145344#M5808). 4. Run `python generate_library.py -v locally_generated -o YOUR_ORG_ID -k YOUR_API_KEY` making these replacements: -* Replace `YOUR_ORG_ID` with the org ID you want to use as reference. Use the one opted into Early API access if you want the beta endpoints. + +* Replace `YOUR_ORG_ID` with the org ID you want to use as reference. Use the one opted into Early API access if you + want the beta endpoints. * Replace `YOUR_API_KEY` with an API key that has org admin privileges on that org. -* NB: Your local system may require minor syntax tweaks (e.g. Windows may require you prepend `generate_library.py` with `.\`) -5. You will now have a `meraki` module folder inside `generator`, which you can locally reference in your scripts. Simply copy the `meraki` folder to those projects which require it. -6. In some cases, if you've already installed the official library, your scripts may prefer that one over the local folder. If that happens, then calls to early access endpoints will fail. So, if necessary, uninstall any instances of the meraki package that may have been installed in your venv or system, or replace the version installed in your venv with that which you generated here. +* NB: Your local system may require minor syntax tweaks (e.g. Windows may require you prepend `generate_library.py` + with `.\`) + +5. You will now have a `meraki` module folder inside `generator`, which you can locally reference in your scripts. + Simply copy the `meraki` folder to those projects which require it. +6. In some cases, if you've already installed the official library, your scripts may prefer that one over the local + folder. If that happens, then calls to early access endpoints will fail. So, if necessary, uninstall any instances of + the meraki package that may have been installed in your venv or system, or replace the version installed in your venv + with that which you generated here. diff --git a/meraki/__init__.py b/meraki/__init__.py index e734ca8c..ab296cee 100644 --- a/meraki/__init__.py +++ b/meraki/__init__.py @@ -1,24 +1,21 @@ import logging import os -from meraki.rest_session import * from meraki.api.administered import Administered -from meraki.api.organizations import Organizations -from meraki.api.networks import Networks -from meraki.api.devices import Devices from meraki.api.appliance import Appliance +# Batch class imports +from meraki.api.batch import Batch from meraki.api.camera import Camera from meraki.api.cellularGateway import CellularGateway +from meraki.api.devices import Devices from meraki.api.insight import Insight from meraki.api.licensing import Licensing +from meraki.api.networks import Networks +from meraki.api.organizations import Organizations from meraki.api.sensor import Sensor from meraki.api.sm import Sm from meraki.api.switch import Switch from meraki.api.wireless import Wireless - -# Batch class imports -from meraki.api.batch import Batch - # Config import from meraki.config import ( API_KEY_ENVIRONMENT_VARIABLE, @@ -44,6 +41,7 @@ MERAKI_PYTHON_SDK_CALLER, USE_ITERATOR_FOR_GET_PAGES, ) +from meraki.rest_session import * __version__ = '1.38.0' diff --git a/meraki/aio/__init__.py b/meraki/aio/__init__.py index 4ee66147..c0c5cbac 100644 --- a/meraki/aio/__init__.py +++ b/meraki/aio/__init__.py @@ -1,7 +1,6 @@ import logging import os -from meraki.aio.rest_session import * from meraki.aio.api.administered import AsyncAdministered from meraki.aio.api.appliance import AsyncAppliance from meraki.aio.api.camera import AsyncCamera @@ -15,10 +14,9 @@ from meraki.aio.api.sm import AsyncSm from meraki.aio.api.switch import AsyncSwitch from meraki.aio.api.wireless import AsyncWireless - +from meraki.aio.rest_session import * # Batch class imports from meraki.api.batch import Batch - # Config import from meraki.config import ( API_KEY_ENVIRONMENT_VARIABLE, diff --git a/meraki/aio/rest_session.py b/meraki/aio/rest_session.py index c597b5b1..25eb2bb3 100644 --- a/meraki/aio/rest_session.py +++ b/meraki/aio/rest_session.py @@ -1,12 +1,12 @@ -import random -import time import asyncio import json -import sys +import random import ssl - +import sys +import time import urllib.parse from datetime import datetime + import aiohttp from meraki.__init__ import __version__ diff --git a/meraki/common.py b/meraki/common.py index 1dd3c8de..801d1f47 100644 --- a/meraki/common.py +++ b/meraki/common.py @@ -5,7 +5,10 @@ def check_python_version(): # Check minimum Python version - if not (int(platform.python_version_tuple()[0]) == 3 and int(platform.python_version_tuple()[1]) >= 8): + if not ( + int(platform.python_version_tuple()[0]) == 3 + and int(platform.python_version_tuple()[1]) >= 8 + ): message = ( f"This library requires Python 3.8 at minimum. Python versions 3.7 and below " f"no longer receive security updates since reaching end of life and of support " diff --git a/meraki/config.py b/meraki/config.py index ce8c2c15..43d33434 100644 --- a/meraki/config.py +++ b/meraki/config.py @@ -1,19 +1,19 @@ # Package Constants # Meraki dashboard API key, set either at instantiation or as an environment variable -API_KEY_ENVIRONMENT_VARIABLE = 'MERAKI_DASHBOARD_API_KEY' +API_KEY_ENVIRONMENT_VARIABLE = "MERAKI_DASHBOARD_API_KEY" # Base URL preceding all endpoint resources -DEFAULT_BASE_URL = 'https://api.meraki.com/api/v1' +DEFAULT_BASE_URL = "https://api.meraki.com/api/v1" # Maximum number of seconds for each API call SINGLE_REQUEST_TIMEOUT = 60 # Path for TLS/SSL certificate verification if behind local proxy -CERTIFICATE_PATH = '' +CERTIFICATE_PATH = "" # Proxy server and port, if needed, for HTTPS -REQUESTS_PROXY = '' +REQUESTS_PROXY = "" # Retry if 429 rate limit error encountered? WAIT_ON_RATE_LIMIT = True @@ -40,10 +40,10 @@ OUTPUT_LOG = True # Path to output log; by default, working directory of script if not specified -LOG_PATH = '' +LOG_PATH = "" # Log file name appended with date and timestamp -LOG_FILE_PREFIX = 'meraki_api_' +LOG_FILE_PREFIX = "meraki_api_" # Print output logging to console? PRINT_TO_CONSOLE = True @@ -65,7 +65,7 @@ AIO_MAXIMUM_CONCURRENT_REQUESTS = 8 # Legacy partner identifier for API usage tracking; can also be set as an environment variable BE_GEO_ID -BE_GEO_ID = '' +BE_GEO_ID = "" # Optional identifier for API usage tracking; can also be set as an environment variable MERAKI_PYTHON_SDK_CALLER # It's good practice to use this to identify your application using the format: @@ -83,4 +83,4 @@ # don't want to report version number, then you would use, at minimum: 'SunshineRainbows hunter2ForLife' # The choice is yours as long as you follow the format. You should **not** include other information in this string. # If you are an official ecosystem partner, this is required. -MERAKI_PYTHON_SDK_CALLER = '' +MERAKI_PYTHON_SDK_CALLER = "" diff --git a/meraki/exceptions.py b/meraki/exceptions.py index 55475dda..0130f3cd 100644 --- a/meraki/exceptions.py +++ b/meraki/exceptions.py @@ -104,7 +104,6 @@ class PythonVersionError(Exception): """Exception raised for unsupported Python versions.""" def __init__(self, message): - self.message = message super().__init__(self.message) diff --git a/meraki/rest_session.py b/meraki/rest_session.py index 2f4e5a5c..dd788088 100644 --- a/meraki/rest_session.py +++ b/meraki/rest_session.py @@ -1,12 +1,12 @@ -import random -import time -import requests import json +import random import sys - +import time import urllib.parse from datetime import datetime +import requests + from meraki.__init__ import __version__ from meraki.common import * from meraki.config import * diff --git a/tests/test_dashboard_api_python_library.py b/tests/test_dashboard_api_python_library.py index cdd0158b..e0b44148 100644 --- a/tests/test_dashboard_api_python_library.py +++ b/tests/test_dashboard_api_python_library.py @@ -1,8 +1,9 @@ +import platform +import random + import pytest + import meraki -import random -import platform -import time @pytest.fixture(scope='session') From 8a350b2cc29ce5a7a04401ac00d7d2e6f2dfb163 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Tue, 7 Nov 2023 16:16:31 -0800 Subject: [PATCH 08/16] Automatically regenerated library. (#230) Co-authored-by: GitHub Action --- meraki/__init__.py | 2 +- meraki/aio/api/devices.py | 6 +- meraki/aio/api/organizations.py | 27 ++++++- meraki/aio/api/sm.py | 7 +- meraki/aio/api/switch.py | 27 +++++++ meraki/aio/api/wireless.py | 126 ++++++++++++++++++++++++++++++++ meraki/api/devices.py | 6 +- meraki/api/organizations.py | 27 ++++++- meraki/api/sm.py | 7 +- meraki/api/switch.py | 27 +++++++ meraki/api/wireless.py | 126 ++++++++++++++++++++++++++++++++ 11 files changed, 373 insertions(+), 15 deletions(-) diff --git a/meraki/__init__.py b/meraki/__init__.py index ab296cee..2d15f7ec 100644 --- a/meraki/__init__.py +++ b/meraki/__init__.py @@ -43,7 +43,7 @@ ) from meraki.rest_session import * -__version__ = '1.38.0' +__version__ = '1.39.0' class DashboardAPI(object): diff --git a/meraki/aio/api/devices.py b/meraki/aio/api/devices.py index 6e33ce6b..25092e51 100644 --- a/meraki/aio/api/devices.py +++ b/meraki/aio/api/devices.py @@ -166,6 +166,7 @@ def createDeviceLiveToolsPing(self, serial: str, target: str, **kwargs): - serial (string): Serial - target (string): FQDN, IPv4 or IPv6 address - count (integer): Count parameter to pass to ping. [1..5], default 5 + - callback (object): Details for the callback. Please include either an httpServerId OR url and sharedSecret """ kwargs.update(locals()) @@ -177,7 +178,7 @@ def createDeviceLiveToolsPing(self, serial: str, target: str, **kwargs): serial = urllib.parse.quote(str(serial), safe='') resource = f'/devices/{serial}/liveTools/ping' - body_params = ['target', 'count', ] + body_params = ['target', 'count', 'callback', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) @@ -212,6 +213,7 @@ def createDeviceLiveToolsPingDevice(self, serial: str, **kwargs): - serial (string): Serial - count (integer): Count parameter to pass to ping. [1..5], default 5 + - callback (object): Details for the callback. Please include either an httpServerId OR url and sharedSecret """ kwargs.update(locals()) @@ -223,7 +225,7 @@ def createDeviceLiveToolsPingDevice(self, serial: str, **kwargs): serial = urllib.parse.quote(str(serial), safe='') resource = f'/devices/{serial}/liveTools/pingDevice' - body_params = ['count', ] + body_params = ['count', 'callback', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) diff --git a/meraki/aio/api/organizations.py b/meraki/aio/api/organizations.py index c0db1e4c..436dd873 100644 --- a/meraki/aio/api/organizations.py +++ b/meraki/aio/api/organizations.py @@ -123,6 +123,7 @@ def createOrganizationActionBatch(self, organizationId: str, actions: list, **kw - actions (array): A set of changes to make as part of this action (more details) - confirmed (boolean): Set to true for immediate execution. Set to false if the action should be previewed before executing. This property cannot be unset once it is true. Defaults to false. - synchronous (boolean): Set to true to force the batch to run synchronous. There can be at most 20 actions in synchronous batch. Defaults to false. + - callback (object): Details for the callback. Please include either an httpServerId OR url and sharedSecret """ kwargs.update(locals()) @@ -134,7 +135,7 @@ def createOrganizationActionBatch(self, organizationId: str, actions: list, **kw organizationId = urllib.parse.quote(str(organizationId), safe='') resource = f'/organizations/{organizationId}/actionBatches' - body_params = ['confirmed', 'synchronous', 'actions', ] + body_params = ['confirmed', 'synchronous', 'actions', 'callback', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) @@ -2268,6 +2269,7 @@ def getOrganizationInventoryOnboardingCloudMonitoringNetworks(self, organization - deviceType (string): Device Type switch or wireless controller - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages - direction (string): direction to paginate, either "next" (default) or "prev" page + - search (string): Optional parameter to search on network name - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 100000. Default is 1000. - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. @@ -2286,7 +2288,7 @@ def getOrganizationInventoryOnboardingCloudMonitoringNetworks(self, organization organizationId = urllib.parse.quote(str(organizationId), safe='') resource = f'/organizations/{organizationId}/inventory/onboarding/cloudMonitoring/networks' - query_params = ['deviceType', 'perPage', 'startingAfter', 'endingBefore', ] + query_params = ['deviceType', 'search', 'perPage', 'startingAfter', 'endingBefore', ] params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} return self._session.get_pages(metadata, resource, params, total_pages, direction) @@ -3597,6 +3599,27 @@ def getOrganizationWebhooksAlertTypes(self, organizationId: str, **kwargs): + def getOrganizationWebhooksCallbacksStatus(self, organizationId: str, callbackId: str): + """ + **Return the status of an API callback** + https://developer.cisco.com/meraki/api-v1/#!get-organization-webhooks-callbacks-status + + - organizationId (string): Organization ID + - callbackId (string): Callback ID + """ + + metadata = { + 'tags': ['organizations', 'configure', 'webhooks', 'callbacks', 'statuses'], + 'operation': 'getOrganizationWebhooksCallbacksStatus' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + callbackId = urllib.parse.quote(str(callbackId), safe='') + resource = f'/organizations/{organizationId}/webhooks/callbacks/statuses/{callbackId}' + + return self._session.get(metadata, resource) + + + def getOrganizationWebhooksLogs(self, organizationId: str, total_pages=1, direction='next', **kwargs): """ **Return the log of webhook POSTs sent** diff --git a/meraki/aio/api/sm.py b/meraki/aio/api/sm.py index f2e9cee4..7ea3cee8 100644 --- a/meraki/aio/api/sm.py +++ b/meraki/aio/api/sm.py @@ -72,6 +72,7 @@ def getNetworkSmDevices(self, networkId: str, total_pages=1, direction='next', * - wifiMacs (array): Filter devices by wifi mac(s). - serials (array): Filter devices by serial(s). - ids (array): Filter devices by id(s). + - uuids (array): Filter devices by uuid(s). - scope (array): Specify a scope (one of all, none, withAny, withAll, withoutAny, or withoutAll) and a set of tags. - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. @@ -87,10 +88,10 @@ def getNetworkSmDevices(self, networkId: str, total_pages=1, direction='next', * networkId = urllib.parse.quote(str(networkId), safe='') resource = f'/networks/{networkId}/sm/devices' - query_params = ['fields', 'wifiMacs', 'serials', 'ids', 'scope', 'perPage', 'startingAfter', 'endingBefore', ] + query_params = ['fields', 'wifiMacs', 'serials', 'ids', 'uuids', 'scope', 'perPage', 'startingAfter', 'endingBefore', ] params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} - array_params = ['fields', 'wifiMacs', 'serials', 'ids', 'scope', ] + array_params = ['fields', 'wifiMacs', 'serials', 'ids', 'uuids', 'scope', ] for k, v in kwargs.items(): if k.strip() in array_params: params[f'{k.strip()}[]'] = kwargs[f'{k}'] @@ -663,7 +664,7 @@ def getNetworkSmDeviceWlanLists(self, networkId: str, deviceId: str): def getNetworkSmProfiles(self, networkId: str): """ - **List all the profiles in the network** + **List all profiles in a network** https://developer.cisco.com/meraki/api-v1/#!get-network-sm-profiles - networkId (string): Network ID diff --git a/meraki/aio/api/switch.py b/meraki/aio/api/switch.py index 209ee468..ad49aab1 100644 --- a/meraki/aio/api/switch.py +++ b/meraki/aio/api/switch.py @@ -2472,6 +2472,33 @@ def updateOrganizationConfigTemplateSwitchProfilePort(self, organizationId: str, + def getOrganizationSummarySwitchPowerHistory(self, organizationId: str, **kwargs): + """ + **Returns the total PoE power draw for all switch ports in the organization over the requested timespan (by default the last 24 hours)** + https://developer.cisco.com/meraki/api-v1/#!get-organization-summary-switch-power-history + + - organizationId (string): Organization ID + - t0 (string): The beginning of the timespan for the data. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 31 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be less than or equal to 31 days. The default is 1 day. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['switch', 'monitor', 'summary', 'power', 'history'], + 'operation': 'getOrganizationSummarySwitchPowerHistory' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/summary/switch/power/history' + + query_params = ['t0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + return self._session.get(metadata, resource, params) + + + def cloneOrganizationSwitchDevices(self, organizationId: str, sourceSerial: str, targetSerials: list): """ **Clone port-level and some switch-level configuration settings from a source switch to one or more target switches** diff --git a/meraki/aio/api/wireless.py b/meraki/aio/api/wireless.py index 762686c5..df523ec8 100644 --- a/meraki/aio/api/wireless.py +++ b/meraki/aio/api/wireless.py @@ -2429,3 +2429,129 @@ def getOrganizationWirelessDevicesEthernetStatuses(self, organizationId: str, to return self._session.get_pages(metadata, resource, params, total_pages, direction) + + + def getOrganizationWirelessDevicesPacketLossByClient(self, organizationId: str, total_pages=1, direction='next', **kwargs): + """ + **Get average packet loss for the given timespan for all clients in the organization.** + https://developer.cisco.com/meraki/api-v1/#!get-organization-wireless-devices-packet-loss-by-client + + - organizationId (string): Organization ID + - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages + - direction (string): direction to paginate, either "next" (default) or "prev" page + - networkIds (array): Filter results by network. + - ssids (array): Filter results by SSID number. + - bands (array): Filter results by band. Valid bands are: 2.4, 5, and 6. + - macs (array): Filter results by client mac address(es). + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. + - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 90 days from today. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 90 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be greater than or equal to 5 minutes and be less than or equal to 90 days. The default is 7 days. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['wireless', 'monitor', 'devices', 'packetLoss', 'byClient'], + 'operation': 'getOrganizationWirelessDevicesPacketLossByClient' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/wireless/devices/packetLoss/byClient' + + query_params = ['networkIds', 'ssids', 'bands', 'macs', 'perPage', 'startingAfter', 'endingBefore', 't0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + array_params = ['networkIds', 'ssids', 'bands', 'macs', ] + for k, v in kwargs.items(): + if k.strip() in array_params: + params[f'{k.strip()}[]'] = kwargs[f'{k}'] + params.pop(k.strip()) + + return self._session.get_pages(metadata, resource, params, total_pages, direction) + + + + def getOrganizationWirelessDevicesPacketLossByDevice(self, organizationId: str, total_pages=1, direction='next', **kwargs): + """ + **Get average packet loss for the given timespan for all devices in the organization** + https://developer.cisco.com/meraki/api-v1/#!get-organization-wireless-devices-packet-loss-by-device + + - organizationId (string): Organization ID + - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages + - direction (string): direction to paginate, either "next" (default) or "prev" page + - networkIds (array): Filter results by network. + - serials (array): Filter results by device. + - ssids (array): Filter results by SSID number. + - bands (array): Filter results by band. Valid bands are: 2.4, 5, and 6. + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. + - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 90 days from today. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 90 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be greater than or equal to 5 minutes and be less than or equal to 90 days. The default is 7 days. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['wireless', 'monitor', 'devices', 'packetLoss', 'byDevice'], + 'operation': 'getOrganizationWirelessDevicesPacketLossByDevice' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/wireless/devices/packetLoss/byDevice' + + query_params = ['networkIds', 'serials', 'ssids', 'bands', 'perPage', 'startingAfter', 'endingBefore', 't0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + array_params = ['networkIds', 'serials', 'ssids', 'bands', ] + for k, v in kwargs.items(): + if k.strip() in array_params: + params[f'{k.strip()}[]'] = kwargs[f'{k}'] + params.pop(k.strip()) + + return self._session.get_pages(metadata, resource, params, total_pages, direction) + + + + def getOrganizationWirelessDevicesPacketLossByNetwork(self, organizationId: str, total_pages=1, direction='next', **kwargs): + """ + **Get average packet loss for the given timespan for all networks in the organization.** + https://developer.cisco.com/meraki/api-v1/#!get-organization-wireless-devices-packet-loss-by-network + + - organizationId (string): Organization ID + - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages + - direction (string): direction to paginate, either "next" (default) or "prev" page + - networkIds (array): Filter results by network. + - serials (array): Filter results by device. + - ssids (array): Filter results by SSID number. + - bands (array): Filter results by band. Valid bands are: 2.4, 5, and 6. + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. + - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 90 days from today. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 90 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be greater than or equal to 5 minutes and be less than or equal to 90 days. The default is 7 days. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['wireless', 'monitor', 'devices', 'packetLoss', 'byNetwork'], + 'operation': 'getOrganizationWirelessDevicesPacketLossByNetwork' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/wireless/devices/packetLoss/byNetwork' + + query_params = ['networkIds', 'serials', 'ssids', 'bands', 'perPage', 'startingAfter', 'endingBefore', 't0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + array_params = ['networkIds', 'serials', 'ssids', 'bands', ] + for k, v in kwargs.items(): + if k.strip() in array_params: + params[f'{k.strip()}[]'] = kwargs[f'{k}'] + params.pop(k.strip()) + + return self._session.get_pages(metadata, resource, params, total_pages, direction) + diff --git a/meraki/api/devices.py b/meraki/api/devices.py index 2e3976d0..874a031b 100644 --- a/meraki/api/devices.py +++ b/meraki/api/devices.py @@ -166,6 +166,7 @@ def createDeviceLiveToolsPing(self, serial: str, target: str, **kwargs): - serial (string): Serial - target (string): FQDN, IPv4 or IPv6 address - count (integer): Count parameter to pass to ping. [1..5], default 5 + - callback (object): Details for the callback. Please include either an httpServerId OR url and sharedSecret """ kwargs.update(locals()) @@ -177,7 +178,7 @@ def createDeviceLiveToolsPing(self, serial: str, target: str, **kwargs): serial = urllib.parse.quote(str(serial), safe='') resource = f'/devices/{serial}/liveTools/ping' - body_params = ['target', 'count', ] + body_params = ['target', 'count', 'callback', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) @@ -212,6 +213,7 @@ def createDeviceLiveToolsPingDevice(self, serial: str, **kwargs): - serial (string): Serial - count (integer): Count parameter to pass to ping. [1..5], default 5 + - callback (object): Details for the callback. Please include either an httpServerId OR url and sharedSecret """ kwargs.update(locals()) @@ -223,7 +225,7 @@ def createDeviceLiveToolsPingDevice(self, serial: str, **kwargs): serial = urllib.parse.quote(str(serial), safe='') resource = f'/devices/{serial}/liveTools/pingDevice' - body_params = ['count', ] + body_params = ['count', 'callback', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) diff --git a/meraki/api/organizations.py b/meraki/api/organizations.py index a2055231..d00caa54 100644 --- a/meraki/api/organizations.py +++ b/meraki/api/organizations.py @@ -123,6 +123,7 @@ def createOrganizationActionBatch(self, organizationId: str, actions: list, **kw - actions (array): A set of changes to make as part of this action (more details) - confirmed (boolean): Set to true for immediate execution. Set to false if the action should be previewed before executing. This property cannot be unset once it is true. Defaults to false. - synchronous (boolean): Set to true to force the batch to run synchronous. There can be at most 20 actions in synchronous batch. Defaults to false. + - callback (object): Details for the callback. Please include either an httpServerId OR url and sharedSecret """ kwargs.update(locals()) @@ -134,7 +135,7 @@ def createOrganizationActionBatch(self, organizationId: str, actions: list, **kw organizationId = urllib.parse.quote(str(organizationId), safe='') resource = f'/organizations/{organizationId}/actionBatches' - body_params = ['confirmed', 'synchronous', 'actions', ] + body_params = ['confirmed', 'synchronous', 'actions', 'callback', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) @@ -2268,6 +2269,7 @@ def getOrganizationInventoryOnboardingCloudMonitoringNetworks(self, organization - deviceType (string): Device Type switch or wireless controller - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages - direction (string): direction to paginate, either "next" (default) or "prev" page + - search (string): Optional parameter to search on network name - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 100000. Default is 1000. - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. @@ -2286,7 +2288,7 @@ def getOrganizationInventoryOnboardingCloudMonitoringNetworks(self, organization organizationId = urllib.parse.quote(str(organizationId), safe='') resource = f'/organizations/{organizationId}/inventory/onboarding/cloudMonitoring/networks' - query_params = ['deviceType', 'perPage', 'startingAfter', 'endingBefore', ] + query_params = ['deviceType', 'search', 'perPage', 'startingAfter', 'endingBefore', ] params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} return self._session.get_pages(metadata, resource, params, total_pages, direction) @@ -3597,6 +3599,27 @@ def getOrganizationWebhooksAlertTypes(self, organizationId: str, **kwargs): + def getOrganizationWebhooksCallbacksStatus(self, organizationId: str, callbackId: str): + """ + **Return the status of an API callback** + https://developer.cisco.com/meraki/api-v1/#!get-organization-webhooks-callbacks-status + + - organizationId (string): Organization ID + - callbackId (string): Callback ID + """ + + metadata = { + 'tags': ['organizations', 'configure', 'webhooks', 'callbacks', 'statuses'], + 'operation': 'getOrganizationWebhooksCallbacksStatus' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + callbackId = urllib.parse.quote(str(callbackId), safe='') + resource = f'/organizations/{organizationId}/webhooks/callbacks/statuses/{callbackId}' + + return self._session.get(metadata, resource) + + + def getOrganizationWebhooksLogs(self, organizationId: str, total_pages=1, direction='next', **kwargs): """ **Return the log of webhook POSTs sent** diff --git a/meraki/api/sm.py b/meraki/api/sm.py index bea94079..4f6db685 100644 --- a/meraki/api/sm.py +++ b/meraki/api/sm.py @@ -72,6 +72,7 @@ def getNetworkSmDevices(self, networkId: str, total_pages=1, direction='next', * - wifiMacs (array): Filter devices by wifi mac(s). - serials (array): Filter devices by serial(s). - ids (array): Filter devices by id(s). + - uuids (array): Filter devices by uuid(s). - scope (array): Specify a scope (one of all, none, withAny, withAll, withoutAny, or withoutAll) and a set of tags. - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. @@ -87,10 +88,10 @@ def getNetworkSmDevices(self, networkId: str, total_pages=1, direction='next', * networkId = urllib.parse.quote(str(networkId), safe='') resource = f'/networks/{networkId}/sm/devices' - query_params = ['fields', 'wifiMacs', 'serials', 'ids', 'scope', 'perPage', 'startingAfter', 'endingBefore', ] + query_params = ['fields', 'wifiMacs', 'serials', 'ids', 'uuids', 'scope', 'perPage', 'startingAfter', 'endingBefore', ] params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} - array_params = ['fields', 'wifiMacs', 'serials', 'ids', 'scope', ] + array_params = ['fields', 'wifiMacs', 'serials', 'ids', 'uuids', 'scope', ] for k, v in kwargs.items(): if k.strip() in array_params: params[f'{k.strip()}[]'] = kwargs[f'{k}'] @@ -663,7 +664,7 @@ def getNetworkSmDeviceWlanLists(self, networkId: str, deviceId: str): def getNetworkSmProfiles(self, networkId: str): """ - **List all the profiles in the network** + **List all profiles in a network** https://developer.cisco.com/meraki/api-v1/#!get-network-sm-profiles - networkId (string): Network ID diff --git a/meraki/api/switch.py b/meraki/api/switch.py index ba6e036f..8902a3e3 100644 --- a/meraki/api/switch.py +++ b/meraki/api/switch.py @@ -2472,6 +2472,33 @@ def updateOrganizationConfigTemplateSwitchProfilePort(self, organizationId: str, + def getOrganizationSummarySwitchPowerHistory(self, organizationId: str, **kwargs): + """ + **Returns the total PoE power draw for all switch ports in the organization over the requested timespan (by default the last 24 hours)** + https://developer.cisco.com/meraki/api-v1/#!get-organization-summary-switch-power-history + + - organizationId (string): Organization ID + - t0 (string): The beginning of the timespan for the data. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 31 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be less than or equal to 31 days. The default is 1 day. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['switch', 'monitor', 'summary', 'power', 'history'], + 'operation': 'getOrganizationSummarySwitchPowerHistory' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/summary/switch/power/history' + + query_params = ['t0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + return self._session.get(metadata, resource, params) + + + def cloneOrganizationSwitchDevices(self, organizationId: str, sourceSerial: str, targetSerials: list): """ **Clone port-level and some switch-level configuration settings from a source switch to one or more target switches** diff --git a/meraki/api/wireless.py b/meraki/api/wireless.py index 9db3b63e..06041097 100644 --- a/meraki/api/wireless.py +++ b/meraki/api/wireless.py @@ -2429,3 +2429,129 @@ def getOrganizationWirelessDevicesEthernetStatuses(self, organizationId: str, to return self._session.get_pages(metadata, resource, params, total_pages, direction) + + + def getOrganizationWirelessDevicesPacketLossByClient(self, organizationId: str, total_pages=1, direction='next', **kwargs): + """ + **Get average packet loss for the given timespan for all clients in the organization.** + https://developer.cisco.com/meraki/api-v1/#!get-organization-wireless-devices-packet-loss-by-client + + - organizationId (string): Organization ID + - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages + - direction (string): direction to paginate, either "next" (default) or "prev" page + - networkIds (array): Filter results by network. + - ssids (array): Filter results by SSID number. + - bands (array): Filter results by band. Valid bands are: 2.4, 5, and 6. + - macs (array): Filter results by client mac address(es). + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. + - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 90 days from today. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 90 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be greater than or equal to 5 minutes and be less than or equal to 90 days. The default is 7 days. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['wireless', 'monitor', 'devices', 'packetLoss', 'byClient'], + 'operation': 'getOrganizationWirelessDevicesPacketLossByClient' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/wireless/devices/packetLoss/byClient' + + query_params = ['networkIds', 'ssids', 'bands', 'macs', 'perPage', 'startingAfter', 'endingBefore', 't0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + array_params = ['networkIds', 'ssids', 'bands', 'macs', ] + for k, v in kwargs.items(): + if k.strip() in array_params: + params[f'{k.strip()}[]'] = kwargs[f'{k}'] + params.pop(k.strip()) + + return self._session.get_pages(metadata, resource, params, total_pages, direction) + + + + def getOrganizationWirelessDevicesPacketLossByDevice(self, organizationId: str, total_pages=1, direction='next', **kwargs): + """ + **Get average packet loss for the given timespan for all devices in the organization** + https://developer.cisco.com/meraki/api-v1/#!get-organization-wireless-devices-packet-loss-by-device + + - organizationId (string): Organization ID + - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages + - direction (string): direction to paginate, either "next" (default) or "prev" page + - networkIds (array): Filter results by network. + - serials (array): Filter results by device. + - ssids (array): Filter results by SSID number. + - bands (array): Filter results by band. Valid bands are: 2.4, 5, and 6. + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. + - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 90 days from today. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 90 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be greater than or equal to 5 minutes and be less than or equal to 90 days. The default is 7 days. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['wireless', 'monitor', 'devices', 'packetLoss', 'byDevice'], + 'operation': 'getOrganizationWirelessDevicesPacketLossByDevice' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/wireless/devices/packetLoss/byDevice' + + query_params = ['networkIds', 'serials', 'ssids', 'bands', 'perPage', 'startingAfter', 'endingBefore', 't0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + array_params = ['networkIds', 'serials', 'ssids', 'bands', ] + for k, v in kwargs.items(): + if k.strip() in array_params: + params[f'{k.strip()}[]'] = kwargs[f'{k}'] + params.pop(k.strip()) + + return self._session.get_pages(metadata, resource, params, total_pages, direction) + + + + def getOrganizationWirelessDevicesPacketLossByNetwork(self, organizationId: str, total_pages=1, direction='next', **kwargs): + """ + **Get average packet loss for the given timespan for all networks in the organization.** + https://developer.cisco.com/meraki/api-v1/#!get-organization-wireless-devices-packet-loss-by-network + + - organizationId (string): Organization ID + - total_pages (integer or string): use with perPage to get total results up to total_pages*perPage; -1 or "all" for all pages + - direction (string): direction to paginate, either "next" (default) or "prev" page + - networkIds (array): Filter results by network. + - serials (array): Filter results by device. + - ssids (array): Filter results by SSID number. + - bands (array): Filter results by band. Valid bands are: 2.4, 5, and 6. + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000. + - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 90 days from today. + - t1 (string): The end of the timespan for the data. t1 can be a maximum of 90 days after t0. + - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be greater than or equal to 5 minutes and be less than or equal to 90 days. The default is 7 days. + """ + + kwargs.update(locals()) + + metadata = { + 'tags': ['wireless', 'monitor', 'devices', 'packetLoss', 'byNetwork'], + 'operation': 'getOrganizationWirelessDevicesPacketLossByNetwork' + } + organizationId = urllib.parse.quote(str(organizationId), safe='') + resource = f'/organizations/{organizationId}/wireless/devices/packetLoss/byNetwork' + + query_params = ['networkIds', 'serials', 'ssids', 'bands', 'perPage', 'startingAfter', 'endingBefore', 't0', 't1', 'timespan', ] + params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} + + array_params = ['networkIds', 'serials', 'ssids', 'bands', ] + for k, v in kwargs.items(): + if k.strip() in array_params: + params[f'{k.strip()}[]'] = kwargs[f'{k}'] + params.pop(k.strip()) + + return self._session.get_pages(metadata, resource, params, total_pages, direction) + From 5e98ce0ed170b111d3f4e73ecf4cb1e780dbcb07 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Mon, 13 Nov 2023 18:36:30 -0800 Subject: [PATCH 09/16] Create local_subnet_dumper.py New example script for IPAM. --- examples/local_subnet_dumper.py | 136 ++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 examples/local_subnet_dumper.py diff --git a/examples/local_subnet_dumper.py b/examples/local_subnet_dumper.py new file mode 100644 index 00000000..cfca5a0b --- /dev/null +++ b/examples/local_subnet_dumper.py @@ -0,0 +1,136 @@ +import json + +import meraki + +""" +2023-11-13 +Author: John M. Kuchta ( TKIPisalegacycipher // https://github.com/TKIPisalegacycipher ) +Requrires: Meraki library v1.39.0 or later. + +This script gathers all your appliances' local subnets from your organizations' networks and dumps them to a JSON file. +You might find this handy for certain IPAM exercises. Subnets from appliances in "single LAN" mode will have VLAN 0. + +EXTRA CREDIT +If you are feeling adventurous, or just want an opportunity to flex your Python skills, consider re-writing this script +to work asynchronously, which can substantially improve the speed for large environments. You might start by gathering +all the relevant information asynchronously, then doing the list comprehensions after the API calls are complete. +""" + +# You can exclude specific organization or networks here. This is optional but recommended if you have a large +# deployment including lots of irrelevant networks. +excluded_org_ids = [] +excluded_network_ids = [] + +# init session +d = meraki.DashboardAPI() + +# gather orgs +my_orgs = d.organizations.getOrganizations() +my_orgs = [org for org in my_orgs if org["id"] not in excluded_org_ids] + +print(f"done gathering organizations") + +# gather networks +my_networks = [ + d.organizations.getOrganizationNetworks( + organization["id"], total_pages=all + ) + for organization in my_orgs +] + +print(f"done gathering networks") + +my_appliance_networks = [ + network + for netlist in my_networks + for network in netlist + if network["id"] not in excluded_network_ids + and "appliance" in network["productTypes"] +] + +print(f"done gathering appliance networks") + +# gather routed networks -- appliances in passthrough mode don't have local subnets +my_appliance_routed_networks = [ + network + for network in my_appliance_networks + if d.appliance.getNetworkApplianceSettings(network["id"])["deploymentMode"] + == "routed" +] + +print(f"done gathering routed appliance networks") + +my_appliance_networks_with_vlans = [ + network + for network in my_appliance_routed_networks + if d.appliance.getNetworkApplianceVlansSettings(network["id"])["vlansEnabled"] +] + +print(f"done gathering appliance network vlan settings") + +my_appliance_networks_without_vlans = [ + network + for network in my_appliance_routed_networks + if network not in my_appliance_networks_with_vlans +] + +my_vlan_lists = [ + { + "organizationId": network["organizationId"], + "networkId": network["id"], + "vlans": d.appliance.getNetworkApplianceVlans(network["id"]), + } + for network in my_appliance_networks_with_vlans +] + +print(f"done gathering appliance network vlans") + +my_lans = [ + { + "organizationId": network["organizationId"], + "networkId": network["id"], + "lan": d.appliance.getNetworkApplianceSingleLan(network["id"]), + } + for network in my_appliance_networks_without_vlans +] + +print(f"done gathering appliance network lans") + +# unpack the subnets +vlan_subnets = list() + +for item in my_vlan_lists: + for vlan in item["vlans"]: + this_subnet = dict() + this_subnet["organizationId"] = item["organizationId"] + this_subnet["networkId"] = vlan["networkId"] + this_subnet["subnet"] = vlan["subnet"] + this_subnet["vlanId"] = vlan["id"] + this_subnet["applianceIp"] = vlan["applianceIp"] + vlan_subnets.append(this_subnet) + +lan_subnets = list() + +for item in my_lans: + this_subnet = dict() + this_subnet["organizationId"] = item["organizationId"] + this_subnet["networkId"] = item["networkId"] + this_subnet["subnet"] = item["lan"]["subnet"] + this_subnet["vlanId"] = 0 + this_subnet["applianceIp"] = item["lan"]["applianceIp"] + lan_subnets.append(this_subnet) + +all_subnets = vlan_subnets + lan_subnets + +print("done assembling subnets") + +# dump the subnets to a JSON file +json_object = json.dumps(all_subnets, indent=4) + +with open( + "../../../../../Users/jkuchta/AppData/Roaming/JetBrains/PyCharm2023.2/scratches/subnets.json", + "w", +) as outfile: + outfile.write(json_object) + +print("subnets dumped to subnets.json") From 41fbd1133d6aa97b435523941402e01c30514fd0 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Mon, 13 Nov 2023 18:38:39 -0800 Subject: [PATCH 10/16] Update local_subnet_dumper.py --- examples/local_subnet_dumper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/local_subnet_dumper.py b/examples/local_subnet_dumper.py index cfca5a0b..5a4c2874 100644 --- a/examples/local_subnet_dumper.py +++ b/examples/local_subnet_dumper.py @@ -128,7 +128,7 @@ json_object = json.dumps(all_subnets, indent=4) with open( - "../../../../../Users/jkuchta/AppData/Roaming/JetBrains/PyCharm2023.2/scratches/subnets.json", + "subnets.json", "w", ) as outfile: outfile.write(json_object) From 559e4d3edf9be21c4ee2bb4091afe55ba5aa5b5b Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Fri, 8 Dec 2023 19:16:45 +1100 Subject: [PATCH 11/16] Fix packaging requirement Minimum Python version bumped from 3.7 to 3.8 in setup.py, so subsequent versions of the generated package properly reflect the requirement. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9d4cf00c..d25e289a 100644 --- a/setup.py +++ b/setup.py @@ -49,5 +49,5 @@ def find_version(fname): 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', ], - python_requires='>=3.7' + python_requires='>=3.8' ) From 37cd1369bf570c5b575c9498b4f483f460042f43 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Fri, 8 Dec 2023 20:14:46 +1100 Subject: [PATCH 12/16] Automatically regenerated library. (#232) Co-authored-by: GitHub Action Fixes #231 --- meraki/__init__.py | 2 +- meraki/aio/api/networks.py | 2 +- meraki/aio/api/organizations.py | 2 +- meraki/aio/api/sm.py | 2 +- meraki/api/networks.py | 2 +- meraki/api/organizations.py | 2 +- meraki/api/sm.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/meraki/__init__.py b/meraki/__init__.py index 2d15f7ec..5164c4f0 100644 --- a/meraki/__init__.py +++ b/meraki/__init__.py @@ -43,7 +43,7 @@ ) from meraki.rest_session import * -__version__ = '1.39.0' +__version__ = '1.39.1' class DashboardAPI(object): diff --git a/meraki/aio/api/networks.py b/meraki/aio/api/networks.py index d598e821..c366bf68 100644 --- a/meraki/aio/api/networks.py +++ b/meraki/aio/api/networks.py @@ -246,7 +246,7 @@ def getNetworkClients(self, networkId: str, total_pages=1, direction='next', **k - direction (string): direction to paginate, either "next" (default) or "prev" page - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 31 days from today. - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameter t0. The value must be in seconds and be less than or equal to 31 days. The default is 1 day. - - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 10. + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 5000. Default is 10. - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. - statuses (array): Filters clients based on status. Can be one of 'Online' or 'Offline'. diff --git a/meraki/aio/api/organizations.py b/meraki/aio/api/organizations.py index 436dd873..78bdde55 100644 --- a/meraki/aio/api/organizations.py +++ b/meraki/aio/api/organizations.py @@ -2117,7 +2117,7 @@ def getOrganizationInventoryDevices(self, organizationId: str, total_pages=1, di - usedState (string): Filter results by used or unused inventory. Accepted values are 'used' or 'unused'. - search (string): Search for devices in inventory based on serial number, mac address, or model. - macs (array): Search for devices in inventory based on mac addresses. - - networkIds (array): Search for devices in inventory based on network ids. + - networkIds (array): Search for devices in inventory based on network ids. Use explicit 'null' value to get available devices only. - serials (array): Search for devices in inventory based on serials. - models (array): Search for devices in inventory based on model. - orderNumbers (array): Search for devices in inventory based on order numbers. diff --git a/meraki/aio/api/sm.py b/meraki/aio/api/sm.py index 7ea3cee8..c4ca80b1 100644 --- a/meraki/aio/api/sm.py +++ b/meraki/aio/api/sm.py @@ -68,7 +68,7 @@ def getNetworkSmDevices(self, networkId: str, total_pages=1, direction='next', * ownerEmail, ownerUsername, osBuild, publicIp, phoneNumber, diskInfoJson, deviceCapacity, isManaged, hadMdm, isSupervised, meid, imei, iccid, simCarrierNetwork, cellularDataUsed, isHotspotEnabled, createdAt, batteryEstCharge, quarantined, avName, avRunning, asName, fwName, isRooted, loginRequired, screenLockEnabled, screenLockDelay, autoLoginDisabled, autoTags, hasMdm, hasDesktopAgent, diskEncryptionEnabled, - hardwareEncryptionCaps, passCodeLock, usesHardwareKeystore, androidSecurityPatchVersion, and url. + hardwareEncryptionCaps, passCodeLock, usesHardwareKeystore, androidSecurityPatchVersion, cellular, and url. - wifiMacs (array): Filter devices by wifi mac(s). - serials (array): Filter devices by serial(s). - ids (array): Filter devices by id(s). diff --git a/meraki/api/networks.py b/meraki/api/networks.py index 6326f52a..898f6682 100644 --- a/meraki/api/networks.py +++ b/meraki/api/networks.py @@ -246,7 +246,7 @@ def getNetworkClients(self, networkId: str, total_pages=1, direction='next', **k - direction (string): direction to paginate, either "next" (default) or "prev" page - t0 (string): The beginning of the timespan for the data. The maximum lookback period is 31 days from today. - timespan (number): The timespan for which the information will be fetched. If specifying timespan, do not specify parameter t0. The value must be in seconds and be less than or equal to 31 days. The default is 1 day. - - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 1000. Default is 10. + - perPage (integer): The number of entries per page returned. Acceptable range is 3 - 5000. Default is 10. - startingAfter (string): A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. - endingBefore (string): A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. - statuses (array): Filters clients based on status. Can be one of 'Online' or 'Offline'. diff --git a/meraki/api/organizations.py b/meraki/api/organizations.py index d00caa54..013d1da3 100644 --- a/meraki/api/organizations.py +++ b/meraki/api/organizations.py @@ -2117,7 +2117,7 @@ def getOrganizationInventoryDevices(self, organizationId: str, total_pages=1, di - usedState (string): Filter results by used or unused inventory. Accepted values are 'used' or 'unused'. - search (string): Search for devices in inventory based on serial number, mac address, or model. - macs (array): Search for devices in inventory based on mac addresses. - - networkIds (array): Search for devices in inventory based on network ids. + - networkIds (array): Search for devices in inventory based on network ids. Use explicit 'null' value to get available devices only. - serials (array): Search for devices in inventory based on serials. - models (array): Search for devices in inventory based on model. - orderNumbers (array): Search for devices in inventory based on order numbers. diff --git a/meraki/api/sm.py b/meraki/api/sm.py index 4f6db685..32fcac91 100644 --- a/meraki/api/sm.py +++ b/meraki/api/sm.py @@ -68,7 +68,7 @@ def getNetworkSmDevices(self, networkId: str, total_pages=1, direction='next', * ownerEmail, ownerUsername, osBuild, publicIp, phoneNumber, diskInfoJson, deviceCapacity, isManaged, hadMdm, isSupervised, meid, imei, iccid, simCarrierNetwork, cellularDataUsed, isHotspotEnabled, createdAt, batteryEstCharge, quarantined, avName, avRunning, asName, fwName, isRooted, loginRequired, screenLockEnabled, screenLockDelay, autoLoginDisabled, autoTags, hasMdm, hasDesktopAgent, diskEncryptionEnabled, - hardwareEncryptionCaps, passCodeLock, usesHardwareKeystore, androidSecurityPatchVersion, and url. + hardwareEncryptionCaps, passCodeLock, usesHardwareKeystore, androidSecurityPatchVersion, cellular, and url. - wifiMacs (array): Filter devices by wifi mac(s). - serials (array): Filter devices by serial(s). - ids (array): Filter devices by id(s). From e1007d4106a914000beaeb7915e17e1ef9ec1e89 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Tue, 12 Dec 2023 04:00:07 +1100 Subject: [PATCH 13/16] Automatically regenerated library. (#233) Co-authored-by: GitHub Action --- meraki/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meraki/__init__.py b/meraki/__init__.py index 5164c4f0..2a6f4407 100644 --- a/meraki/__init__.py +++ b/meraki/__init__.py @@ -43,7 +43,7 @@ ) from meraki.rest_session import * -__version__ = '1.39.1' +__version__ = '1.40.0' class DashboardAPI(object): From c5325b28262ef2f132da6257723fdbc242a04fbd Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Tue, 12 Dec 2023 11:58:33 -0800 Subject: [PATCH 14/16] Upgrades required aiohttp version. Addresses CVE-2023-49081. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 88bfff37..9617cf81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp~=3.8.6 +aiohttp~=3.9.0 requests~=2.31.0 meraki~=1.38.0 setuptools~=67.2.0 From 6da3dc3889c1fec8285976151b2ba4fa070c6094 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Tue, 12 Dec 2023 12:23:09 -0800 Subject: [PATCH 15/16] Added pipfile Unblocks dependabot updates. --- Pipfile | 16 ++ Pipfile.lock | 617 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 633 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..41044e82 --- /dev/null +++ b/Pipfile @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +aiohttp = "~=3.9.0" +requests = "~=2.31.0" +meraki = "~=1.38.0" +jinja2 = "==3.1.2" +pytest = "~=7.2.1" + +[dev-packages] + +[requires] +python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..22b0bb19 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,617 @@ +{ + "_meta": { + "hash": { + "sha256": "006defe90174b428fed374855c6662123aecc46ae8994807d560ab365dfd2354" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiohttp": { + "hashes": [ + "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f", + "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c", + "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af", + "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4", + "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a", + "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489", + "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213", + "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01", + "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5", + "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361", + "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26", + "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0", + "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4", + "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8", + "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1", + "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7", + "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6", + "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a", + "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd", + "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4", + "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499", + "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183", + "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544", + "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821", + "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501", + "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f", + "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe", + "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f", + "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672", + "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5", + "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2", + "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57", + "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87", + "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0", + "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f", + "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7", + "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed", + "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70", + "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0", + "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f", + "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d", + "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f", + "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d", + "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431", + "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff", + "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf", + "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83", + "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690", + "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587", + "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e", + "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb", + "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3", + "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66", + "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014", + "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35", + "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f", + "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0", + "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449", + "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23", + "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5", + "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd", + "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4", + "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b", + "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558", + "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd", + "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766", + "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a", + "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636", + "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d", + "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590", + "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e", + "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d", + "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c", + "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28", + "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065", + "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.9.1" + }, + "aiosignal": { + "hashes": [ + "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", + "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, + "attrs": { + "hashes": [ + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1.0" + }, + "certifi": { + "hashes": [ + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.11.17" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.6" + }, + "frozenlist": { + "hashes": [ + "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6", + "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01", + "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251", + "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9", + "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b", + "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87", + "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf", + "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f", + "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0", + "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2", + "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b", + "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc", + "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c", + "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467", + "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9", + "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1", + "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a", + "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79", + "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167", + "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300", + "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf", + "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea", + "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2", + "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab", + "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3", + "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb", + "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087", + "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc", + "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8", + "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62", + "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f", + "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326", + "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c", + "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431", + "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963", + "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7", + "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef", + "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3", + "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956", + "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781", + "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472", + "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc", + "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839", + "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672", + "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3", + "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503", + "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d", + "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8", + "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b", + "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc", + "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f", + "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559", + "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b", + "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95", + "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb", + "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963", + "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919", + "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f", + "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3", + "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1", + "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.0" + }, + "idna": { + "hashes": [ + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + ], + "markers": "python_version >= '3.5'", + "version": "==3.6" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==3.1.2" + }, + "markupsafe": { + "hashes": [ + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.3" + }, + "meraki": { + "hashes": [ + "sha256:2d8c23726e2a65fceb0d2ba343d79ac633cdf73d2a58349f4fe214d5fdf6718d", + "sha256:cd9a7cc92aa2d8243eace96906486e00b8dccac964b1e6695e0495695bae442e" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==1.38.0" + }, + "multidict": { + "hashes": [ + "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9", + "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8", + "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03", + "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710", + "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161", + "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664", + "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569", + "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067", + "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313", + "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706", + "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2", + "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636", + "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49", + "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93", + "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603", + "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0", + "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60", + "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4", + "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e", + "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1", + "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60", + "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951", + "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc", + "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe", + "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95", + "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d", + "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8", + "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed", + "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2", + "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775", + "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87", + "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c", + "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2", + "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98", + "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3", + "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe", + "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78", + "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660", + "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176", + "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e", + "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988", + "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c", + "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c", + "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0", + "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449", + "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f", + "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde", + "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5", + "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d", + "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac", + "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a", + "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9", + "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca", + "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11", + "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35", + "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063", + "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b", + "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982", + "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258", + "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1", + "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52", + "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480", + "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7", + "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461", + "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d", + "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc", + "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779", + "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a", + "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547", + "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0", + "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171", + "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf", + "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d", + "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba" + ], + "markers": "python_version >= '3.7'", + "version": "==6.0.4" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e", + "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.2.2" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "urllib3": { + "hashes": [ + "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", + "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" + ], + "markers": "python_version >= '3.8'", + "version": "==2.1.0" + }, + "yarl": { + "hashes": [ + "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51", + "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce", + "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559", + "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0", + "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81", + "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc", + "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4", + "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c", + "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130", + "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136", + "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e", + "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec", + "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7", + "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1", + "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455", + "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099", + "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129", + "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10", + "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142", + "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98", + "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa", + "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7", + "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525", + "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c", + "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9", + "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c", + "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8", + "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b", + "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf", + "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23", + "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd", + "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27", + "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f", + "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece", + "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434", + "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec", + "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff", + "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78", + "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d", + "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863", + "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53", + "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31", + "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15", + "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5", + "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b", + "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57", + "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3", + "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1", + "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f", + "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad", + "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c", + "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7", + "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2", + "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b", + "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2", + "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b", + "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9", + "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be", + "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e", + "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984", + "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4", + "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074", + "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2", + "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392", + "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91", + "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541", + "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf", + "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572", + "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66", + "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575", + "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14", + "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5", + "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1", + "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e", + "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551", + "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17", + "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead", + "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0", + "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe", + "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234", + "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0", + "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7", + "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34", + "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42", + "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385", + "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78", + "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be", + "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958", + "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749", + "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec" + ], + "markers": "python_version >= '3.7'", + "version": "==1.9.4" + } + }, + "develop": {} +} From d0790fa8817746bf83c79bb9c94d1825491fc4a0 Mon Sep 17 00:00:00 2001 From: "John M. Kuchta" Date: Wed, 13 Dec 2023 07:43:29 +1100 Subject: [PATCH 16/16] Automatically regenerated library. (#234) Co-authored-by: GitHub Action --- meraki/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meraki/__init__.py b/meraki/__init__.py index 2a6f4407..738518ae 100644 --- a/meraki/__init__.py +++ b/meraki/__init__.py @@ -43,7 +43,7 @@ ) from meraki.rest_session import * -__version__ = '1.40.0' +__version__ = '1.40.1' class DashboardAPI(object):