Migrate from cobra¶
Problem — you have working cobra automation and want to port it. This is
the practical mapping: which cobra concept becomes which niwaki call, three
worked ports against the commerce deployment, and the pitfalls that bite in
practice. For the argument, see Why niwaki — a comparison with cobra; for the same tasks in both SDKs
side by side, Side by side — cobra and niwaki.
cobra snippets follow the official documentation and are shown for reference — cobra is not installable from an index, so they are not executed here. The niwaki blocks run.
The concept map¶
cobra |
niwaki |
|---|---|
|
|
build MOs with parent plumbing ( |
designs — detached trees, no parent objects needed (The design DSL) |
relation classes + |
|
|
|
|
|
|
|
|
|
manual |
transparent pagination; |
check-after-commit |
|
Port 1 — provisioning a tenant¶
cobra, per the official examples:
from cobra.mit.access import MoDirectory
from cobra.mit.session import LoginSession
from cobra.mit.request import ConfigRequest
from cobra.model.fv import Tenant, Ctx, BD, RsCtx
ls = LoginSession('https://apic.example.com', 'admin', 'secret')
moDir = MoDirectory(ls)
moDir.login()
uniMo = moDir.lookupByDn('uni')
tenantMo = Tenant(uniMo, 'commerce')
Ctx(tenantMo, 'prod')
bdMo = BD(tenantMo, 'bd-web')
RsCtx(bdMo, tnFvCtxName='prod')
req = ConfigRequest()
req.addMo(tenantMo)
moDir.commit(req)
moDir.logout()
The same, ported:
from niwaki import Niwaki
from niwaki.design import tenant
config = tenant("commerce")
config.vrf("prod")
config.bd("bd-web").bind(vrf="prod")
with Niwaki("https://apic.example.com", "admin", "secret") as aci:
config.push(aci)
What disappeared: the uni lookup (designs are detached — no round trip before
writing), the relation class and its tnFvCtxName string (derived from the
schemas), and the request object. What appeared: closed-world checking —
misspell prod in the bind() and the push fails before any request, with a
did-you-mean. Note the bind() sits on the BD itself, the object that owns the
relation.
Port 2 — a filtered class query¶
tenants = moDir.lookupByClass(
"fvTenant", propFilter='and(eq(fvTenant.name, "commerce"))'
)
aci = Niwaki.connect("https://apic.example.com", "admin", "secret")
config = tenant("commerce")
config.vrf("prod")
config.push(aci)
tenants = aci.query("fvTenant").where(name="commerce").fetch()
assert [t.name for t in tenants] == ["commerce"]
The with form closes the session for you; connect() is used here so the
rest of the page can share one client — see Connection & transport.
Filter kwargs address the APIC attribute names, exactly like the propFilter
string did — but composed and quoted for you, with and_ / or_ / gt
expressions when kwargs are not enough (Observe and verify a fabric).
Port 3 — check-before-write¶
cobra has one write verb, commit(); verification means committing and reading
back. The niwaki port turns that inside out — the check comes first:
change = tenant("commerce").bd("bd-web").set(arp_flooding=True)
plan = change.push(aci, mode="plan")
print(plan.updates or plan.creates) # review artifact — nothing written yet
change.push(aci)
assert change.push(aci, mode="plan").has_changes is False
Pitfalls when porting¶
No ancestor climb on
bind()— a bind attaches to the current cursor, not a parent. PortRsCtx(bdMo, ...)tobd.bind(vrf=...)on the BD, then descend to children (.subnet(...)) after the bind — not before.Wire names live on in two places — query filters and
to_payload()output speak APIC (arpFlood); everything else speaks operator (arp_flooding). A portedpropFilterkeeps its attribute names.No implicit parents — cobra required a live parent MO (
lookupByDn('uni')); designs declare the chain instead, and attribute-less parents are upserts that touch nothing. Do not port the lookups.commit()batches ≠staged— oneConfigRequestis closest tostrict(one POST); usestagedonly when you want per-object progress and its partial-failure semantics (When a push fails).Firmware coupling is gone — your venv no longer tracks the APIC. The models ship with the package (APIC 6.0 schemas); string-name queries still work for classes newer than the shipped schema (
aci.query("newClass")).Session hygiene is automatic — delete the
login()/logout()/ re-auth plumbing; the context manager and proactive token refresh own it (Connection & transport).