Skip to content

Commit f58f4dc

Browse files
Initial release of new action batch functions
The new action batch functions return batch-formatted actions instead of executing the associated changes. Then you can chain the actions together and create an action batch more easily.
1 parent edffa83 commit f58f4dc

4 files changed

Lines changed: 215 additions & 4 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class ActionBatch{{ class_name }}(object):
2+
def __init__(self):
3+
super(ActionBatch{{ class_name }}, self).__init__()
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
def {{ operation }}(self{% if function_definition|length > 0 %}{{ function_definition }}{% endif %}):
2+
"""
3+
**{{ description }}**
4+
{{ doc_url }}
5+
6+
{% for d in descriptions %}
7+
- {{ d }}
8+
{% endfor %}
9+
"""
10+
11+
{% if kwarg_line|length > 0 %}
12+
{{ kwarg_line }}
13+
14+
{% endif %}
15+
{% if assert_blocks|length > 0 %}
16+
{% for param, values in assert_blocks %}
17+
if '{{ param }}' in kwargs:
18+
options = {{ values }}
19+
assert kwargs['{{ param }}'] in options, f'''"{{ param }}" cannot be "{kwargs['{{ param }}']}", & must be set to one of: {options}'''
20+
{% endfor %}
21+
22+
{% endif %}
23+
metadata = {
24+
'tags': {{ tags }},
25+
'operation': '{{ operation }}'
26+
}
27+
resource = f'{{ resource }}'
28+
29+
{% if query_params|length > 0 %}
30+
query_params = [{% for param in query_params %}'{{ param }}', {% endfor %}]
31+
params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params}
32+
33+
{% endif %}
34+
{% if array_params|length > 0 %}
35+
array_params = [{% for param in array_params %}'{{ param }}', {% endfor %}]
36+
for k, v in kwargs.items():
37+
if k.strip() in array_params:
38+
params[f'{k.strip()}[]'] = kwargs[f'{k}']
39+
params.pop(k.strip())
40+
41+
{% endif %}
42+
{% if body_params|length > 0 %}
43+
body_params = [{% for param in body_params %}'{{ param }}', {% endfor %}]
44+
payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params}
45+
{% endif %}
46+
action = {
47+
"resource": resource,
48+
"operation": "{{ batch_operation }}",
49+
"body": payload
50+
}
51+
{{ call_line }}
52+
53+
54+
55+
56+

generator/generate_library.py

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,10 @@ def generate_library(spec, version_number):
130130
tags = spec['tags']
131131
paths = spec['paths']
132132
scopes = {tag['name']: {} for tag in tags[:10]}
133+
batchable_action_summaries = [action['summary'] for action in spec['x-batchable-actions']]
133134

134135
# Check paths and create sub-directories if needed
135-
subdirs = ['meraki', 'meraki/api', 'meraki/aio', 'meraki/aio/api']
136+
subdirs = ['meraki', 'meraki/api', 'meraki/api/batch', 'meraki/aio', 'meraki/aio/api', 'meraki/api/batch']
136137
for dir in subdirs:
137138
if not os.path.isdir(dir):
138139
os.mkdir(dir)
@@ -182,6 +183,7 @@ def generate_library(spec, version_number):
182183
)
183184
)
184185

186+
# Generate Asyncio API libraries
185187
async_output = open(f'meraki/aio/api/{scope}.py', 'w', encoding='utf-8')
186188
with open('async_class_template.jinja2', encoding='utf-8') as fp:
187189
class_template = fp.read()
@@ -192,6 +194,18 @@ def generate_library(spec, version_number):
192194
)
193195
)
194196

197+
# Generate Action Batch API libraries
198+
batch_output = open(f'meraki/api/batch/{scope}.py', 'w', encoding='utf-8')
199+
with open('batch_class_template.jinja2', encoding='utf-8') as fp:
200+
class_template = fp.read()
201+
template = jinja_env.from_string(class_template)
202+
batch_output.write(
203+
template.render(
204+
class_name=scope[0].upper() + scope[1:],
205+
)
206+
)
207+
208+
# Generate API & Asyncio API functions
195209
for path, methods in section.items():
196210
for method, endpoint in methods.items():
197211
# Get metadata
@@ -299,7 +313,7 @@ def generate_library(spec, version_number):
299313
query_params=query_params,
300314
array_params=array_params,
301315
body_params=body_params,
302-
call_line=call_line,
316+
call_line=call_line
303317
)
304318
)
305319
async_output.write(
@@ -318,10 +332,113 @@ def generate_library(spec, version_number):
318332
query_params=query_params,
319333
array_params=array_params,
320334
body_params=body_params,
321-
call_line=call_line,
335+
call_line=call_line
322336
)
323337
)
324338

339+
# Generate API action batch functions
340+
for path, methods in section.items():
341+
for method, endpoint in methods.items():
342+
if endpoint['description'] in batchable_action_summaries:
343+
# Get metadata
344+
tags = endpoint['tags']
345+
operation = endpoint['operationId']
346+
description = endpoint['summary']
347+
parameters = endpoint['parameters'] if 'parameters' in endpoint else None
348+
responses = endpoint['responses'] # not actually used here for library generation
349+
350+
# Function definition
351+
definition = ''
352+
if parameters:
353+
for p, values in parse_params(operation, parameters, 'required').items():
354+
if values['type'] == 'array':
355+
definition += f', {p}: list'
356+
elif values['type'] == 'number':
357+
definition += f', {p}: float'
358+
elif values['type'] == 'integer':
359+
definition += f', {p}: int'
360+
elif values['type'] == 'boolean':
361+
definition += f', {p}: bool'
362+
elif values['type'] == 'object':
363+
definition += f', {p}: dict'
364+
elif values['type'] == 'string':
365+
definition += f', {p}: str'
366+
367+
if 'perPage' in parse_params(operation, parameters):
368+
if operation in REVERSE_PAGINATION:
369+
definition += ", total_pages=1, direction='prev'"
370+
else:
371+
definition += ", total_pages=1, direction='next'"
372+
if operation == 'getNetworkEvents':
373+
definition += ', event_log_end_time=None'
374+
375+
if parse_params(operation, parameters, ['optional']):
376+
definition += f', **kwargs'
377+
378+
# Docstring
379+
param_descriptions = []
380+
all_params = parse_params(operation, parameters, ['required', 'pagination', 'optional'])
381+
if all_params:
382+
for p, values in all_params.items():
383+
param_descriptions.append(f'{p} ({values["type"]}): {values["description"]}')
384+
385+
# Combine keyword args with locals
386+
kwarg_line = ''
387+
if parse_params(operation, parameters, ['optional']):
388+
kwarg_line = 'kwargs.update(locals())'
389+
elif parse_params(operation, parameters, ['query', 'array', 'body']):
390+
kwarg_line = 'kwargs = locals()'
391+
392+
# Assert valid values for enum
393+
enum_params = parse_params(operation, parameters, ['enum'])
394+
assert_blocks = []
395+
if enum_params:
396+
for p, values in enum_params.items():
397+
assert_blocks.append((p, values['enum']))
398+
399+
# Function body for GET endpoints
400+
query_params = array_params = body_params = {}
401+
402+
# Function body for POST/PUT endpoints
403+
if method == 'post' or method == 'put':
404+
body_params = parse_params(operation, parameters, 'body')
405+
if method == 'post':
406+
batch_operation = 'create'
407+
else:
408+
batch_operation = 'update'
409+
410+
# Function body for DELETE endpoints
411+
elif method == 'delete':
412+
batch_operation = 'destroy'
413+
414+
# Function return statement
415+
call_line = 'return action'
416+
417+
# Add function to files
418+
with open('batch_function_template.jinja2', encoding='utf-8') as fp:
419+
function_template = fp.read()
420+
template = jinja_env.from_string(function_template)
421+
batch_output.write(
422+
'\n\n' +
423+
template.render(
424+
operation=operation,
425+
function_definition=definition,
426+
description=description,
427+
doc_url=docs_url(operation),
428+
descriptions=param_descriptions,
429+
kwarg_line=kwarg_line,
430+
all_params=list(all_params.keys()),
431+
assert_blocks=assert_blocks,
432+
tags=tags,
433+
resource=path,
434+
query_params=query_params,
435+
array_params=array_params,
436+
body_params=body_params,
437+
call_line=call_line,
438+
batch_operation=batch_operation
439+
)
440+
)
441+
325442

326443
# Prints READ_ME help message for user to read
327444
def print_help():

meraki/__init__.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,29 @@
1313
from .api.sm import Sm
1414
from .api.switch import Switch
1515
from .api.wireless import Wireless
16+
17+
# Batch class imports
18+
from .api.batch.organizations import ActionBatchOrganizations
19+
from .api.batch.networks import ActionBatchNetworks
20+
from .api.batch.devices import ActionBatchDevices
21+
from .api.batch.appliance import ActionBatchAppliance
22+
from .api.batch.camera import ActionBatchCamera
23+
from .api.batch.cellularGateway import ActionBatchCellularGateway
24+
from .api.batch.insight import ActionBatchInsight
25+
from .api.batch.sm import ActionBatchSm
26+
from .api.batch.switch import ActionBatchSwitch
27+
from .api.batch.wireless import ActionBatchWireless
28+
29+
# Config import
1630
from .config import (
1731
API_KEY_ENVIRONMENT_VARIABLE, DEFAULT_BASE_URL, SINGLE_REQUEST_TIMEOUT, CERTIFICATE_PATH, REQUESTS_PROXY,
1832
WAIT_ON_RATE_LIMIT, NGINX_429_RETRY_WAIT_TIME, ACTION_BATCH_RETRY_WAIT_TIME, RETRY_4XX_ERROR,
1933
RETRY_4XX_ERROR_WAIT_TIME, MAXIMUM_RETRIES, OUTPUT_LOG, LOG_PATH, LOG_FILE_PREFIX, PRINT_TO_CONSOLE,
2034
SUPPRESS_LOGGING, SIMULATE_API_CALLS, BE_GEO_ID, MERAKI_PYTHON_SDK_CALLER
2135
)
2236

23-
__version__ = '1.6.2'
37+
__version__ = '1.6.3a'
38+
2439

2540
class DashboardAPI(object):
2641
"""
@@ -129,3 +144,23 @@ def __init__(self, api_key=None, base_url=DEFAULT_BASE_URL, single_request_timeo
129144
self.sm = Sm(self._session)
130145
self.switch = Switch(self._session)
131146
self.wireless = Wireless(self._session)
147+
148+
# Batch class
149+
class Batch:
150+
def __init__(self):
151+
pass
152+
153+
# Batch definitions
154+
self.batch = Batch()
155+
156+
# Action Batch API endpoints by section
157+
self.batch.organizations = ActionBatchOrganizations()
158+
self.batch.networks = ActionBatchNetworks()
159+
self.batch.devices = ActionBatchDevices()
160+
self.batch.appliance = ActionBatchAppliance()
161+
self.batch.camera = ActionBatchCamera()
162+
self.batch.cellularGateway = ActionBatchCellularGateway()
163+
self.batch.insight = ActionBatchInsight()
164+
self.batch.sm = ActionBatchSm()
165+
self.batch.switch = ActionBatchSwitch()
166+
self.batch.wireless = ActionBatchWireless()

0 commit comments

Comments
 (0)