Skip to content

Commit 4e2ec1a

Browse files
rest_session.py extractions and minor improvements (#296)
* Extractathon! * Extracting parts of rest_session.py into common.py * Creating input validation for caller and be_geo_id arguments * Improving help text on config.py * Update pyproject.toml Tested and verified working on pytest 8.3.5. Bumping version requirement. * Fix missing return statement * Clean up rest_session.py extractions
1 parent 08b4576 commit 4e2ec1a

File tree

8 files changed

+179
-107
lines changed

8 files changed

+179
-107
lines changed

meraki/aio/rest_session.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import time
77
import urllib.parse
8-
from datetime import datetime
8+
from datetime import datetime, timezone
99

1010
import aiohttp
1111

@@ -66,18 +66,14 @@ def __init__(
6666
check_python_version()
6767

6868
# Check base URL
69-
if "v0" in self._base_url:
70-
sys.exit(f'This library does not support dashboard API v0 ({self._base_url} was configured as the base'
71-
f' URL). API v0 has been end of life since 2020 August 5.')
72-
elif self._base_url[-1] == "/":
73-
self._base_url = self._base_url[:-1]
69+
reject_v0_base_url(self)
7470

7571
# Update the headers for the session
7672
self._headers = {
7773
"Authorization": "Bearer " + self._api_key,
7874
"Content-Type": "application/json",
7975
"User-Agent": f"python-meraki/aio-{self._version} "
80-
+ user_agent_extended(self._be_geo_id, self._caller),
76+
+ validate_user_agent(self._be_geo_id, self._caller),
8177
}
8278
if self._certificate_path:
8379
self._sslcontext = ssl.create_default_context()

meraki/common.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import platform
22
from meraki.exceptions import *
3+
import re
4+
import sys
5+
import urllib.parse
36

47

58
def check_python_version():
@@ -20,3 +23,63 @@ def check_python_version():
2023
)
2124

2225
raise PythonVersionError(message)
26+
27+
28+
def validate_user_agent(be_geo_id, caller):
29+
# Generate extended portion of the User Agent
30+
# Validate that it follows the expected format
31+
user_agent = dict()
32+
33+
allowed_format_in_regex = r'^[A-Za-z0-9]+(?:/[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*(-[a-z]+)?)? [A-Za-z-0-9]+$'
34+
35+
if caller and re.match(allowed_format_in_regex, caller):
36+
user_agent["caller"] = caller
37+
elif be_geo_id and re.match(allowed_format_in_regex, be_geo_id):
38+
user_agent["caller"] = be_geo_id
39+
else:
40+
if caller:
41+
message = "Please follow the user agent format prescribed in our User Agents guide, available here:"
42+
doc_link = "https://developer.cisco.com/meraki/api-v1/user-agents-overview/"
43+
raise SessionInputError("MERAKI_PTYHON_SDK_CALLER", caller, message, doc_link)
44+
elif be_geo_id:
45+
message = "Use of be_geo_id is deprecated. Please use the argument MERAKI_PTYHON_SDK_CALLER instead."
46+
doc_link = "https://developer.cisco.com/meraki/api-v1/user-agents-overview/"
47+
raise SessionInputError("BE_GEO_ID", caller, message, doc_link)
48+
else:
49+
user_agent["caller"] = "unidentified"
50+
51+
caller_string = f'Caller/({user_agent["caller"]})'
52+
53+
return caller_string
54+
55+
56+
def reject_v0_base_url(self):
57+
if 'v0' in self._base_url:
58+
sys.exit(f'This library does not support dashboard API v0 ({self._base_url} was configured as the base'
59+
f' URL). API v0 has been end of life since 2020 August 5.')
60+
elif self._base_url[-1] == '/':
61+
self._base_url = self._base_url[:-1]
62+
63+
64+
def iterator_for_get_pages_bool(self):
65+
return self._use_iterator_for_get_pages
66+
67+
68+
def use_iterator_for_get_pages_setter(self, value):
69+
if value:
70+
self.get_pages = self._get_pages_iterator
71+
else:
72+
self.get_pages = self._get_pages_legacy
73+
74+
self._use_iterator_for_get_pages = value
75+
76+
77+
def validate_base_url(self, url):
78+
allowed_domains = ['meraki.com', 'meraki.ca', 'meraki.cn', 'meraki.in', 'gov-meraki.com']
79+
parsed_url = urllib.parse.urlparse(url)
80+
if any(domain in parsed_url.netloc for domain in allowed_domains):
81+
abs_url = url
82+
else:
83+
abs_url = self._base_url + url
84+
return abs_url
85+

meraki/config.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,15 @@
8282
# 1. Application name precedes vendor name in all cases.
8383
# 2. If your application or vendor name normally contains spaces or special casing, you should omit them in favor of
8484
# normal CamelCasing here.
85-
# 3. The optional slash and version number are optional. Leave both out if you like.
85+
# 3. The slash and version number are optional. Leave both out if you like.
8686
# 4. The slash is a forward slash, '/' -- not a backslash.
87-
# 5. Don't use the 'Meraki' name in your application name here. Maybe in general? I'm a config file, not a lawyer.
87+
# 5. Don't use the 'Meraki' or 'Cisco' names in your application name here. Maybe in general? I'm a config file, not a
88+
# lawyer.
8889
# Example 1: if your application is named 'Mambo', version number is 5.0, and your vendor/company name is Vega, then
8990
# you would use, at minimum: 'Mambo Vega'. Optionally: 'Mambo/5.0 Vega'.
9091
# Example 2: if your application is named 'Sunshine Rainbows', and company name is 'hunter2 for Life', and if you
9192
# don't want to report version number, then you would use, at minimum: 'SunshineRainbows hunter2ForLife'
9293
# The choice is yours as long as you follow the format. You should **not** include other information in this string.
9394
# If you are an official ecosystem partner, this is required.
95+
# For more guidance, please refer to https://developer.cisco.com/meraki/api-v1/user-agents-overview/
9496
MERAKI_PYTHON_SDK_CALLER = ""

meraki/exception_handler.py

Whitespace-only changes.

meraki/exceptions.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def __init__(self, metadata, response):
5757
except ValueError:
5858
self.message = self.response.content[:100].decode("UTF-8").strip()
5959
if (
60-
type(self.message) == str
60+
isinstance(self.message, str)
6161
and self.status == 404
6262
and self.reason == "Not Found"
6363
):
@@ -85,7 +85,7 @@ def __init__(self, metadata, response, message):
8585
response.reason if response is not None and response.reason else None
8686
)
8787
self.message = message
88-
if type(self.message) == str:
88+
if isinstance(self.message, str):
8989
self.message = self.message.strip()
9090
if self.status == 404 and self.reason == "Not Found":
9191
self.message += (
@@ -107,3 +107,15 @@ def __init__(self, message):
107107
self.message = message
108108

109109
super().__init__(self.message)
110+
111+
112+
class SessionInputError(Exception):
113+
"""Exception raised for unsupported session inputs."""
114+
115+
def __init__(self, argument, value, message, doc_link):
116+
self.argument = argument
117+
self.value = value
118+
self.message = message
119+
self.doc_link = doc_link
120+
121+
super().__init__(f'{self.message} {self.doc_link}')

meraki/response_handler.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def handle_3xx(self, response):
2+
abs_url = response.headers['Location']
3+
substring = 'meraki.com/api/v'
4+
if substring not in abs_url:
5+
substring = 'meraki.cn/api/v'
6+
self._base_url = abs_url[:abs_url.find(substring) + len(substring) + 1]
7+
return abs_url
8+

0 commit comments

Comments
 (0)