merakiops 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ # This workflow will upload a Python Package to PyPI when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3
+
4
+ # This workflow uses actions that are not certified by GitHub.
5
+ # They are provided by a third-party and are governed by
6
+ # separate terms of service, privacy policy, and support
7
+ # documentation.
8
+
9
+ name: Upload Python Package
10
+
11
+ on:
12
+ push:
13
+ tags:
14
+ - v*
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ release-build:
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: "3.x"
29
+
30
+ - name: Build release distributions
31
+ run: |
32
+ # NOTE: put your own distribution build steps here.
33
+ python -m pip install build
34
+ python -m build
35
+
36
+ - name: Upload distributions
37
+ uses: actions/upload-artifact@v4
38
+ with:
39
+ name: release-dists
40
+ path: dist/
41
+
42
+ pypi-publish:
43
+ runs-on: ubuntu-latest
44
+ needs:
45
+ - release-build
46
+ permissions:
47
+ # IMPORTANT: this permission is mandatory for trusted publishing
48
+ id-token: write
49
+
50
+ # Dedicated environments with protections for publishing are strongly recommended.
51
+ # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
52
+ environment:
53
+ name: pypi
54
+ # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
55
+ # url: https://pypi.org/p/YOURPROJECT
56
+ #
57
+ # ALTERNATIVE: if your GitHub Release name is the PyPI project version string
58
+ # ALTERNATIVE: exactly, uncomment the following line instead:
59
+ # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}
60
+
61
+ steps:
62
+ - name: Retrieve release distributions
63
+ uses: actions/download-artifact@v4
64
+ with:
65
+ name: release-dists
66
+ path: dist/
67
+
68
+ - name: Publish release distributions to PyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+ with:
71
+ packages-dir: dist/
@@ -0,0 +1,60 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ wheels/
12
+ .eggs/
13
+ pip-wheel-metadata/
14
+ share/python-wheels/
15
+ MANIFEST
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ env/
21
+ ENV/
22
+
23
+ # Distribution / packaging
24
+ .Python
25
+
26
+ # Testing
27
+ .tox/
28
+ .nox/
29
+ .coverage
30
+ .coverage.*
31
+ .cache
32
+ nosetests.xml
33
+ coverage.xml
34
+ *.cover
35
+ *.py,cover
36
+ htmlcov/
37
+ .pytest_cache/
38
+ pytestdebug.log
39
+
40
+ # Type checking
41
+ .mypy_cache/
42
+ .dmypy.json
43
+ dmypy.json
44
+ .pytype/
45
+
46
+ # Hatch
47
+ .hatch/
48
+
49
+ # IDE / editors
50
+ .idea/
51
+ .vscode/
52
+ *.swp
53
+ *.swo
54
+ *~
55
+ .DS_Store
56
+
57
+ # Secrets / local config
58
+ .env
59
+ .env.*
60
+ *.toml.local
@@ -0,0 +1,261 @@
1
+ # CLAUDE.md — merakiops
2
+
3
+ ---
4
+
5
+ ## Purpose
6
+
7
+ You are assisting in building **merakiops**: a Python library for creating, submitting, and verifying Meraki API action batches.
8
+
9
+ merakiops is built on top of **merakisync** and works with its typed model objects (`MerakiObj` subclasses). A merakisync object — a `Switchport`, `Vlan`, `Network`, etc. — is the starting point for every Action.
10
+
11
+ This library has exactly one purpose: **make it safe and reliable to batch configuration changes across thousands of Meraki networks.**
12
+
13
+ ---
14
+
15
+ ## Developer context
16
+
17
+ The developer is a network engineer managing hundreds of Meraki networks. They:
18
+
19
+ - Value reliability and correctness above all else.
20
+ - Will hand this library to junior engineers and network engineers with minimal Python experience.
21
+ - Expect every function, parameter, and error message to be self-explanatory without reading source code.
22
+ - Want to be able to push changes to thousands of networks and verify the results programmatically.
23
+
24
+ ---
25
+
26
+ ## Guiding philosophy
27
+
28
+ ### 1. UNIX philosophy — do one thing well
29
+
30
+ This project handles exactly one concern: create, send, and verify Meraki action batches. Do not add scheduling, retry loops, reporting, alerting, or workflow orchestration. Those belong in the calling application.
31
+
32
+ ### 2. Readable over clever
33
+
34
+ This library will be used by people with minimal Python experience. Prioritize clarity at every level:
35
+
36
+ - Simple > abstraction
37
+ - Explicit > implicit
38
+ - Named keyword arguments over positional where ambiguity exists
39
+ - Error messages that explain what to do, not just what went wrong
40
+ - No magic — no `__init_subclass__`, dynamic class factories, or metaprogramming
41
+
42
+ ### 3. Production mindset
43
+
44
+ Assume thousands of networks. Failures must be:
45
+ - Logged before they propagate (log and re-raise; never swallow silently)
46
+ - Distinguishable — which batch? which action? which resource?
47
+ - Debuggable without a debugger attached
48
+
49
+ ### 4. Safety by default
50
+
51
+ Action batches modify live production network infrastructure. Every default should minimize risk:
52
+
53
+ - `confirmed=False` by default — batches do not execute until `confirm()` is called
54
+ - `synchronous=False` by default
55
+ - No silent retries
56
+ - Explicit errors when a batch has already been submitted
57
+
58
+ ---
59
+
60
+ ## Project structure
61
+
62
+ ```
63
+ src/merakiops/
64
+ ├── __init__.py Public re-exports: Action, ActionBatch, Mismatch, VerifyResult.
65
+ ├── __about__.py Version string only.
66
+ ├── action.py Action class. One action = one API call within a batch.
67
+ ├── action_batch.py ActionBatch class. Submission, confirmation, status, verification.
68
+ └── result.py VerifyResult and Mismatch dataclasses.
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Data flow
74
+
75
+ ```
76
+ MerakiObj (from merakisync)
77
+
78
+ └─► Action.update(obj) # changed fields only, single-resource path
79
+ Action.create(obj) # all fields, collection path
80
+ Action.destroy(obj) # no body, single-resource path
81
+
82
+ └─► ActionBatch.from_actions(org_id, actions)
83
+
84
+ └─► batch.create() # POST /organizations/{id}/actionBatches
85
+ batch.confirm() # PUT /organizations/{id}/actionBatches/{id}
86
+ batch.status() # GET /organizations/{id}/actionBatches/{id}
87
+ batch.verify() # model.get(source="meraki") + field comparison
88
+ ```
89
+
90
+ No raw dicts passed to callers. No API calls outside the ActionBatch methods.
91
+
92
+ ---
93
+
94
+ ## Action
95
+
96
+ ### Rules
97
+
98
+ - Always created from a factory classmethod: `Action.update()`, `Action.create()`, `Action.destroy()`
99
+ - `frozen=True` — an Action must not change after creation
100
+ - `source_obj` is always stored when using factory classmethods — required for `verify()`
101
+ - `body` is `None` for destroy operations; `to_meraki_action()` omits the key entirely
102
+
103
+ ### Valid operations
104
+
105
+ | Operation | When to use | Resource path | Body |
106
+ |---|---|---|---|
107
+ | `update` | Modify fields on an existing resource | Single-resource path | Changed fields only (camelCase) |
108
+ | `create` | Create a new resource | Collection path | All fields (camelCase) |
109
+ | `destroy` | Delete a resource | Single-resource path | None (omitted) |
110
+
111
+ ### create() and resource paths
112
+
113
+ `Action.create()` derives the collection path by stripping the last segment from `obj.resource_path`. This works for: `Network`, `Device`, `Switchport`, `Vlan`, `Ssid`, `Organization`.
114
+
115
+ `L3FirewallRule` and `DhcpServerPolicy` do not support create — use `Action.update()` for both. Their `resource_path` is already the collection/single endpoint used for updates.
116
+
117
+ ---
118
+
119
+ ## ActionBatch
120
+
121
+ ### Rules
122
+
123
+ - Always created via `ActionBatch.from_actions()`, never instantiated directly
124
+ - Splits actions automatically: 100 actions per batch (async) or 20 (synchronous)
125
+ - `confirmed=False` default — batches do not execute until `confirm()` is called
126
+ - `synchronous=False` default
127
+ - `create()` sleeps 5 seconds after submission by default (pass `sleep_seconds=0` to disable)
128
+ - Each batch instance can only be submitted once — `create()` raises `RuntimeError` if `id` is set
129
+ - All methods that call the Meraki API accept an optional `dashboard` argument
130
+
131
+ ### Method lifecycle
132
+
133
+ ```
134
+ ActionBatch.run(org_id, actions) — full lifecycle: create → confirm → wait → verify
135
+ returns a single combined VerifyResult
136
+
137
+ ActionBatch.from_actions() — creates batch objects; splits if needed
138
+
139
+ └─► create() — submits to Meraki; sets self.id; sleeps 5s
140
+ └─► confirm() — executes the batch (only needed if confirmed=False)
141
+ └─► wait_until_complete() — polls until completed/failed (async batches only)
142
+ └─► status() — single poll of current batch state
143
+ └─► verify() — returns VerifyResult for this batch
144
+
145
+ ActionBatch.wait_for_all(batches) — polls all batches together until all finish
146
+ ActionBatch.verify_many(batches) — returns {batch: VerifyResult} with minimal API calls
147
+ ```
148
+
149
+ ### Async vs synchronous timing
150
+
151
+ For **async batches** (`synchronous=False`, the default), `create()` returns as soon as Meraki accepts the batch. Changes have not been applied yet. Always call `wait_until_complete()` or `wait_for_all()` before `verify()` or `verify_many()`, otherwise verify will compare against the pre-change state and report everything as mismatched.
152
+
153
+ For **synchronous batches** (`synchronous=True`), `create()` blocks until all actions complete. `wait_until_complete()` and `wait_for_all()` are no-ops for these batches.
154
+
155
+ ### VerifyResult and Mismatch
156
+
157
+ All verify methods return `VerifyResult` (from `result.py`):
158
+ - `result.verified: list[Action]`
159
+ - `result.mismatched: list[Mismatch]` — each has `.action` and `.mismatches` (camelCase field → diff dict)
160
+ - `result.unverifiable: list[Action]`
161
+ - `result.batch_errors: list[str]` — Meraki execution errors from `batch.errors`
162
+
163
+ `run()` returns a single combined `VerifyResult` across all batches.
164
+ `verify_many()` returns `{batch: VerifyResult}`.
165
+ `verify()` returns a `VerifyResult` for that batch only.
166
+
167
+ ### verify behavior
168
+
169
+ - Uses bulk fetching: 1 API call per org (Device/Network), 1 per serial (Switchport), 1 per network (Vlan/Ssid/L3FirewallRule/DhcpServerPolicy)
170
+ - `verify_many()` and `run()` are preferred for 10+ actions — pools fetches across all batches
171
+ - merakisync model.get() manages its own API connectivity; no `dashboard` arg accepted
172
+ - Supported models: `Network`, `Device`, `Switchport`, `Vlan`, `Ssid`, `L3FirewallRule`, `DhcpServerPolicy`, `Organization`
173
+ - Actions without `source_obj` → "unverifiable" (not an error)
174
+ - Unsupported model types → "unverifiable" (not an error)
175
+ - API fetch errors for a group → all actions in that group become "unverifiable"
176
+ - Destroy operations: resource not found → "verified"; resource still exists → "mismatched"
177
+
178
+ ---
179
+
180
+ ## Adding a model to the verify registry
181
+
182
+ When merakisync adds a new model that supports action batch operations:
183
+
184
+ 1. Add a deferred import inside `_fetch_current_state()` in `action_batch.py`
185
+ 2. Add an `elif cls is NewModel:` branch that calls `NewModel.get(source="meraki", ...)`
186
+ 3. Return `True, result` (supported) or `True, None` (supported but not found)
187
+ 4. Update the supported models list in this file, `README.md`, and `docs/usage.md`
188
+
189
+ ---
190
+
191
+ ## Import rules
192
+
193
+ - merakiops imports from merakisync's submodules directly, not from the merakisync package root
194
+ ```python
195
+ # Correct
196
+ from merakisync.dashboard import get_dashboard
197
+
198
+ # Wrong — may cause circular imports in some environments
199
+ from merakisync import get_dashboard
200
+ ```
201
+ - All Meraki API calls in `create()`, `confirm()`, `status()` go through `get_dashboard()`
202
+ - Model imports in `_fetch_current_state()` are deferred (inside the function body)
203
+ - `get_dashboard()` is imported inside the method body where it is used, not at the top of the file
204
+
205
+ ---
206
+
207
+ ## Meraki API limits
208
+
209
+ | Batch type | Max actions per batch |
210
+ |---|---|
211
+ | Asynchronous (`synchronous=False`) | 100 |
212
+ | Synchronous (`synchronous=True`) | 20 |
213
+
214
+ `from_actions()` handles splitting. You do not need to count actions manually.
215
+
216
+ ---
217
+
218
+ ## Before writing code
219
+
220
+ Always, without exception:
221
+
222
+ 1. **State your reasoning** — explain why the change is needed, what problem it solves, and what alternatives you considered and rejected. Do not skip this even for small changes.
223
+ 2. **State your assumptions** — call out anything you are inferring about merakisync behavior, Meraki API behavior, or existing code contracts. If something is unclear, say so explicitly before starting.
224
+ 3. **Write an implementation plan** — list the specific files and lines you intend to touch, in order. For any change that affects a public method signature, also list the docstring, README, and docs/usage.md sections that need updating.
225
+ 4. **Identify risks** — call out any existing behavior that could break, any edge cases the change introduces, and any callers that depend on the current behavior.
226
+ 5. **Check existing patterns** — read the relevant code in both merakiops and merakisync before proposing anything new. Do not invent abstractions that already exist.
227
+ 6. **Confirm alignment** — wait for explicit approval before making any code changes.
228
+
229
+ ---
230
+
231
+ ## Definition of done
232
+
233
+ A change is complete when:
234
+ - It works correctly for the intended use case
235
+ - Error messages are clear and actionable — they tell the user what to do
236
+ - All public methods have docstrings that explain the return value and any exceptions raised
237
+ - Edge cases are handled: empty actions list, already-submitted batch, `None` source_obj
238
+ - `README.md` and `docs/usage.md` reflect any changes to the public API
239
+
240
+ ---
241
+
242
+ ## What not to do
243
+
244
+ - Do not add scheduling, retry logic, or workflow orchestration
245
+ - Do not add `async` code
246
+ - Do not introduce new dependencies without discussion
247
+ - Do not silently swallow exceptions — log and re-raise
248
+ - Do not make Meraki API calls outside of `create()`, `confirm()`, `status()`, and `verify()`
249
+ - Do not accept `source_obj=None` as valid for any factory classmethod
250
+ - Do not call `batch.create()` twice on the same instance
251
+ - Do not submit a batch with 0 actions
252
+ - Do not put credentials in source code
253
+ - Do not import from `merakisync` (the package root) inside method bodies — import from the submodule directly
254
+
255
+ ---
256
+
257
+ ## Reference
258
+
259
+ - Batch limits and chunking: `docs/batch-limits.md`
260
+ - Full usage guide with examples: `docs/usage.md`
261
+ - Meraki action batch API: https://developer.cisco.com/meraki/api-v1/create-organization-action-batch/
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Nathan Anderson <nathanea05@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.