Skip to content

Commit d2285e5

Browse files
author
Shiyue Cheng
committed
Added v1
- renamed previous "meraki" folder to "meraki_v0" - added "meraki_v1" for v1 beta library functionality - added examples for org-wide clients report for v1 - included several new API endpoints/features released recently
1 parent c55dd63 commit d2285e5

196 files changed

Lines changed: 21008 additions & 241 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
11
# Meraki Dashboard API Python Library
22

3-
The new Meraki Dashboard API Python library provides all current Meraki [Dashboard API](https://api.meraki.com/api_docs) calls to interface with the Cisco Meraki cloud-managed platform. The library is supported on Python 3.6 or above, and you can install it via [PyPI](https://pypi.org/project/meraki/):
3+
The Meraki Dashboard API Python library provides all current Meraki [Dashboard API](https://developer.cisco.com/docs/meraki-api-v1) calls to interface with the Cisco Meraki cloud-managed platform. The library is supported on Python 3.6 or above, and you can install it via [PyPI](https://pypi.org/project/meraki/):
44

55
pip install meraki
66

77
## Features
88

9-
This library's goal is to refresh and supplant the legacy module (this repository versions 0.34 and prior) as well as the now-deprecated [SDK](https://github.com/meraki/meraki-python-sdk). Here are some of the features in this revamped library:
9+
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.
1010

11-
* Support for all API endpoints, as it uses the [OpenAPI specification](https://api.meraki.com/api/v0/openapiSpec) to generate source code
11+
* Support for all API endpoints, as it uses the [OpenAPI specification](https://api.meraki.com/api/v1/openapiSpec) to generate source code
1212
* Log all API requests made to a local file as well as on-screen console
13-
* Automatic retries upon 429 rate limit errors, using the [`Retry-After` field](https://developer.cisco.com/meraki/api/#/rest/guides/rate-limit-errors) within response headers
13+
* Automatic retries upon 429 rate limit errors, using the [`Retry-After` field](https://developer.cisco.com/docs/meraki-api-v1/#!rate-limit) within response headers
1414
* Get all (or a specified number of) pages of data with built-in pagination control
15-
* Tweak settings such as the default base URL (for example, to use with V1 and/or mega-proxy)
15+
* Tweak settings such as maximum retries, certificate path, suppress logging, and other options
1616
* Simulate POST/PUT/DELETE calls to preview first, so that network configuration does not get changed
17-
* Includes the legacy module's functions for backward compatibility
17+
* Includes the legacy module's (version 0.34 and prior) functions for backward compatibility
1818

1919
## Setup
2020

2121
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))
2222

2323
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.
2424

25-
3. Although the Meraki dashboard API, as a REST API, can be accessed in various ways, this library uses Python 3.6+. ([get started with Python](https://wiki.python.org/moin/BeginnersGuide/NonProgrammers))
25+
3. Install the latest version of [Python 3](ttps://wiki.python.org/moin/BeginnersGuide/NonProgrammers)
2626

27-
4. After Python 3 is installed, use _pip_ (or an alternative such as _easy_install_) to install the library:
27+
4. Use _pip_ (or an alternative such as _easy_install_) to install the library:
2828
* `pip install meraki`
2929
* If you have both Python3 and Python2 installed, you may need to use `pip3 install meraki`
3030
* If _meraki_ was previously installed, you can upgrade with `pip install --upgrade meraki` or `pip3 install --upgrade meraki`
31+
* You can specify the version of the library, for example `pip install meraki==0.100.2` for v0 or `pip install meraki==1.0.0b1` for v1
3132

3233
## Usage
3334
1. Export your API key as an [environment variable](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html), for example:
@@ -50,7 +51,7 @@ This library's goal is to refresh and supplant the legacy module (this repositor
5051
dashboard = meraki.DashboardAPI()
5152
```
5253

53-
5. Make dashboard API calls in your source code, using the format _client.section.operation_, where _client_ is the name you defined in the previous step (**dashboard** 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:
54+
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:
5455

5556
```python
5657
my_orgs = dashboard.organizations.getOrganizations()
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ async def listOrganization(aiomeraki: meraki.aio.AsyncDashboardAPI, org):
8181

8282
# Stitch together one consolidated CSV per org
8383
output_file = open(f"{folder_name}.csv", mode="w", newline="\n")
84-
field_names = list(field_names)
84+
field_names = ['id', 'mac', 'description', 'ip', 'ip6', 'ip6Local', 'user', 'firstSeen', 'lastSeen', 'manufacturer', 'os', 'recentDeviceSerial', 'recentDeviceName', 'recentDeviceMac', 'ssid', 'vlan', 'switchport', 'usage', 'status', 'notes', 'smInstalled', 'groupPolicy8021x']
8585
field_names.insert(0, "Network Name")
8686
field_names.insert(1, "Network ID")
87+
8788
csv_writer = csv.DictWriter(
8889
output_file,
8990
field_names,
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import csv
2+
from datetime import datetime
3+
import os
4+
import asyncio
5+
6+
import meraki.aio
7+
8+
# Either input your API key below, or set an environment variable
9+
# for example, in Terminal on macOS: export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73
10+
api_key = ""
11+
12+
13+
async def listNetworkClients(aiomeraki: meraki.aio.AsyncDashboardAPI, folder_name, network):
14+
print(f'Finding clients in network {network["name"]}')
15+
try:
16+
# Get list of clients on network, filtering on timespan of last 14 days
17+
clients = await aiomeraki.networks.getNetworkClients(
18+
network["id"],
19+
timespan=60 * 60 * 24 * 14,
20+
perPage=1000,
21+
total_pages="all",
22+
)
23+
except meraki.AsyncAPIError as e:
24+
print(f"Meraki API error: {e}")
25+
except Exception as e:
26+
print(f"some other error: {e}")
27+
else:
28+
if clients:
29+
# Write to file
30+
file_name = f'{network["name"]}.csv'
31+
output_file = open(
32+
f"{folder_name}/{file_name}", mode="w", newline="\n"
33+
)
34+
field_names = clients[0].keys()
35+
csv_writer = csv.DictWriter(
36+
output_file,
37+
field_names,
38+
delimiter=",",
39+
quotechar='"',
40+
quoting=csv.QUOTE_ALL,
41+
)
42+
csv_writer.writeheader()
43+
csv_writer.writerows(clients)
44+
output_file.close()
45+
print(
46+
f"Successfully output {len(clients)} clients' data to file {file_name}"
47+
)
48+
return network["name"], field_names
49+
return network["name"], None
50+
51+
52+
async def listOrganization(aiomeraki: meraki.aio.AsyncDashboardAPI, org):
53+
print(f'Analyzing organization {org["name"]}:')
54+
org_id = org["id"]
55+
56+
# Get list of networks in organization
57+
try:
58+
networks = await aiomeraki.organizations.getOrganizationNetworks(org_id)
59+
except meraki.AsyncAPIError as e:
60+
print(f"Meraki API error: {e}")
61+
return org["name"]
62+
except Exception as e:
63+
print(f"some other error: {e}")
64+
return org["name"]
65+
66+
# Create local folder
67+
todays_date = f"{datetime.now():%Y-%m-%d}"
68+
folder_name = f"Org {org_id} clients {todays_date}"
69+
if folder_name not in os.listdir():
70+
os.mkdir(folder_name)
71+
72+
# Iterate through networks
73+
total = len(networks)
74+
print(f"Iterating through {total} networks in organization {org_id}")
75+
76+
# create a list of all networks in the organization so we can call them all concurrently
77+
networkClientsTasks = [listNetworkClients(aiomeraki, folder_name, net) for net in networks]
78+
for task in asyncio.as_completed(networkClientsTasks):
79+
networkname, field_names = await task
80+
print(f"finished network: {networkname}")
81+
82+
# Stitch together one consolidated CSV per org
83+
output_file = open(f"{folder_name}.csv", mode="w", newline="\n")
84+
field_names = ['id', 'mac', 'description', 'ip', 'ip6', 'ip6Local', 'user', 'firstSeen', 'lastSeen', 'manufacturer', 'os', 'recentDeviceSerial', 'recentDeviceName', 'recentDeviceMac', 'ssid', 'vlan', 'switchport', 'usage', 'status', 'notes', 'smInstalled', 'groupPolicy8021x']
85+
field_names.insert(0, "Network Name")
86+
field_names.insert(1, "Network ID")
87+
88+
csv_writer = csv.DictWriter(
89+
output_file,
90+
field_names,
91+
delimiter=",",
92+
quotechar='"',
93+
quoting=csv.QUOTE_ALL,
94+
)
95+
csv_writer.writeheader()
96+
for net in networks:
97+
file_name = f'{net["name"]}.csv'
98+
if file_name in os.listdir(folder_name):
99+
with open(f"{folder_name}/{file_name}") as input_file:
100+
csv_reader = csv.DictReader(
101+
input_file,
102+
delimiter=",",
103+
quotechar='"',
104+
quoting=csv.QUOTE_ALL,
105+
)
106+
next(csv_reader)
107+
for row in csv_reader:
108+
row["Network Name"] = net["name"]
109+
row["Network ID"] = net["id"]
110+
csv_writer.writerow(row)
111+
return org["name"]
112+
113+
114+
async def main():
115+
# Instantiate a Meraki dashboard API session
116+
# NOTE: you have to use "async with" so that the session will be closed correctly at the end of the usage
117+
async with meraki.aio.AsyncDashboardAPI(
118+
api_key,
119+
base_url="https://api.meraki.com/api/v1",
120+
log_file_prefix=__file__[:-3],
121+
print_console=False,
122+
) as aiomeraki:
123+
# Get list of organizations to which API key has access
124+
organizations = await aiomeraki.organizations.getOrganizations()
125+
126+
# create a list of all organizations so we can call them all concurrently
127+
organizationTasks = [listOrganization(aiomeraki, org) for org in organizations]
128+
for task in asyncio.as_completed(organizationTasks):
129+
# as_completed returns an iterator, so we just have to await the iterator and not call it
130+
organizationName = await task
131+
print(f"finished organization: {organizationName}")
132+
133+
print("Script complete!")
134+
135+
136+
if __name__ == "__main__":
137+
loop = asyncio.get_event_loop()
138+
loop.run_until_complete(main())
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def main():
1515
dashboard = meraki.DashboardAPI(
1616
api_key='',
1717
base_url='https://api-mp.meraki.com/api/v0/',
18+
output_log=True,
1819
log_file_prefix=os.path.basename(__file__)[:-3],
1920
log_path='',
2021
print_console=False
@@ -33,6 +34,9 @@ def main():
3334
networks = dashboard.networks.getOrganizationNetworks(org_id)
3435
except meraki.APIError as e:
3536
print(f'Meraki API error: {e}')
37+
print(f'status code = {e.status}')
38+
print(f'reason = {e.reason}')
39+
print(f'error = {e.message}')
3640
continue
3741
except Exception as e:
3842
print(f'some other error: {e}')
@@ -55,6 +59,9 @@ def main():
5559
clients = dashboard.clients.getNetworkClients(net['id'], timespan=60*60*24*14, perPage=1000, total_pages='all')
5660
except meraki.APIError as e:
5761
print(f'Meraki API error: {e}')
62+
print(f'status code = {e.status}')
63+
print(f'reason = {e.reason}')
64+
print(f'error = {e.message}')
5865
except Exception as e:
5966
print(f'some other error: {e}')
6067
else:
@@ -74,9 +81,10 @@ def main():
7481

7582
# Stitch together one consolidated CSV per org
7683
output_file = open(f'{folder_name}.csv', mode='w', newline='\n')
77-
field_names = list(field_names)
78-
field_names.insert(0, 'Network Name')
79-
field_names.insert(1, 'Network ID')
84+
field_names = ['id', 'mac', 'description', 'ip', 'ip6', 'ip6Local', 'user', 'firstSeen', 'lastSeen', 'manufacturer', 'os', 'recentDeviceSerial', 'recentDeviceName', 'recentDeviceMac', 'ssid', 'vlan', 'switchport', 'usage', 'status', 'notes', 'smInstalled', 'groupPolicy8021x']
85+
field_names.insert(0, "Network Name")
86+
field_names.insert(1, "Network ID")
87+
8088
csv_writer = csv.DictWriter(output_file, field_names, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
8189
csv_writer.writeheader()
8290
for net in networks:

examples/org_wide_clients_v1.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import csv
2+
from datetime import datetime
3+
import os
4+
5+
import meraki
6+
7+
# Either input your API key below by uncommenting line 10 and changing line 16 to api_key=API_KEY,
8+
# or set an environment variable (preferred) to define your API key. The former is insecure and not recommended.
9+
# For example, in Linux/macOS: export MERAKI_DASHBOARD_API_KEY=093b24e85df15a3e66f1fc359f4c48493eaa1b73
10+
# API_KEY = '093b24e85df15a3e66f1fc359f4c48493eaa1b73'
11+
12+
13+
def main():
14+
# Instantiate a Meraki dashboard API session
15+
dashboard = meraki.DashboardAPI(
16+
api_key='',
17+
base_url='https://api-mp.meraki.com/api/v1/',
18+
output_log=True,
19+
log_file_prefix=os.path.basename(__file__)[:-3],
20+
log_path='',
21+
print_console=False
22+
)
23+
24+
# Get list of organizations to which API key has access
25+
organizations = dashboard.organizations.getOrganizations()
26+
27+
# Iterate through list of orgs
28+
for org in organizations:
29+
print(f'\nAnalyzing organization {org["name"]}:')
30+
org_id = org['id']
31+
32+
# Get list of networks in organization
33+
try:
34+
networks = dashboard.organizations.getOrganizationNetworks(org_id)
35+
except meraki.APIError as e:
36+
print(f'Meraki API error: {e}')
37+
print(f'status code = {e.status}')
38+
print(f'reason = {e.reason}')
39+
print(f'error = {e.message}')
40+
continue
41+
except Exception as e:
42+
print(f'some other error: {e}')
43+
continue
44+
45+
# Create local folder
46+
todays_date = f'{datetime.now():%Y-%m-%d}'
47+
folder_name = f'Org {org_id} clients {todays_date}'
48+
if folder_name not in os.listdir():
49+
os.mkdir(folder_name)
50+
51+
# Iterate through networks
52+
total = len(networks)
53+
counter = 1
54+
print(f' - iterating through {total} networks in organization {org_id}')
55+
for net in networks:
56+
print(f'Finding clients in network {net["name"]} ({counter} of {total})')
57+
try:
58+
# Get list of clients on network, filtering on timespan of last 14 days
59+
clients = dashboard.networks.getNetworkClients(net['id'], timespan=60*60*24*14, perPage=1000, total_pages='all')
60+
except meraki.APIError as e:
61+
print(f'Meraki API error: {e}')
62+
print(f'status code = {e.status}')
63+
print(f'reason = {e.reason}')
64+
print(f'error = {e.message}')
65+
except Exception as e:
66+
print(f'some other error: {e}')
67+
else:
68+
if clients:
69+
# Write to file
70+
file_name = f'{net["name"]}.csv'
71+
output_file = open(f'{folder_name}/{file_name}', mode='w', newline='\n')
72+
field_names = clients[0].keys()
73+
csv_writer = csv.DictWriter(output_file, field_names, delimiter=',', quotechar='"',
74+
quoting=csv.QUOTE_ALL)
75+
csv_writer.writeheader()
76+
csv_writer.writerows(clients)
77+
output_file.close()
78+
print(f' - found {len(clients)}')
79+
80+
counter += 1
81+
82+
# Stitch together one consolidated CSV per org
83+
output_file = open(f'{folder_name}.csv', mode='w', newline='\n')
84+
field_names = ['id', 'mac', 'description', 'ip', 'ip6', 'ip6Local', 'user', 'firstSeen', 'lastSeen', 'manufacturer', 'os', 'recentDeviceSerial', 'recentDeviceName', 'recentDeviceMac', 'ssid', 'vlan', 'switchport', 'usage', 'status', 'notes', 'smInstalled', 'groupPolicy8021x']
85+
field_names.insert(0, "Network Name")
86+
field_names.insert(1, "Network ID")
87+
88+
csv_writer = csv.DictWriter(output_file, field_names, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
89+
csv_writer.writeheader()
90+
for net in networks:
91+
file_name = f'{net["name"]}.csv'
92+
if file_name in os.listdir(folder_name):
93+
with open(f'{folder_name}/{file_name}') as input_file:
94+
csv_reader = csv.DictReader(input_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
95+
next(csv_reader)
96+
for row in csv_reader:
97+
row['Network Name'] = net['name']
98+
row['Network ID'] = net['id']
99+
csv_writer.writerow(row)
100+
101+
102+
if __name__ == '__main__':
103+
start_time = datetime.now()
104+
main()
105+
end_time = datetime.now()
106+
print(f'\nScript complete, total runtime {end_time - start_time}')

0 commit comments

Comments
 (0)