Side by side — cobra and niwaki¶
The same eight everyday tasks, written in both SDKs. The cobra snippets follow the official cobra documentation (APIC-hosted wheels, checked 2026-07-11); since cobra is not installable from an index they are shown for reference, while every niwaki block on this page executes as a test in the SDK’s suite.
Note
cobra in this documentation — three pages, three jobs: Why niwaki — a comparison with cobra is the argument (including where cobra remains the right tool); this page shows the tasks side by side; Migrate from cobra is the migration how-to.
1 — Open a session¶
from cobra.mit.access import MoDirectory
from cobra.mit.session import LoginSession
ls = LoginSession('https://apic.example.com', 'admin', 'secret')
moDir = MoDirectory(ls)
moDir.login()
# … your code …
moDir.logout()
from niwaki import Niwaki
aci = Niwaki.connect("https://apic.example.com", "admin", "secret")
Token refresh, mid-session re-login, retries and timeouts are the client’s
job, not yours (Connection & transport). The with form closes the
session for you; connect() is used here so the rest of the page can share
the client.
2 — A BD with a subnet, in a VRF¶
from cobra.model.fv import Tenant, Ctx, BD, RsCtx, Subnet
from cobra.mit.request import ConfigRequest
uniMo = moDir.lookupByDn('uni')
tnMo = Tenant(uniMo, 'acme')
Ctx(tnMo, 'prod')
bdMo = BD(tnMo, 'web')
Subnet(bdMo, '10.30.1.1/24')
RsCtx(bdMo, tnFvCtxName='prod')
req = ConfigRequest()
req.addMo(tnMo)
moDir.commit(req)
from niwaki.design import tenant
config = tenant("acme")
config.vrf("prod")
config.bd("web", unicast_routing=True).bind(vrf="prod").subnet("10.30.1.1/24")
config.push(aci)
No parent lookup, no relation class, no tnFvCtxName — and the bind() is
checked against the design before anything is sent.
3 — Contract wiring¶
from cobra.model.fv import Ap, AEPg, RsBd, RsProv, RsCons
from cobra.model.vz import Filter, Entry, BrCP, Subj, RsSubjFiltAtt
apMo = Ap(tnMo, 'shop')
webMo = AEPg(apMo, 'web')
RsBd(webMo, tnFvBDName='web')
RsProv(webMo, tnVzBrCPName='web-api')
filterMo = Filter(tnMo, 'http')
Entry(filterMo, 'e1', etherT='ip', prot='tcp', dFromPort='8080', dToPort='8080')
brcpMo = BrCP(tnMo, 'web-api')
subjMo = Subj(brcpMo, 's1')
RsSubjFiltAtt(subjMo, tnVzFilterName='http')
epg = config.app("shop").epg("web")
epg.bind(bd="web").provide("web-api")
config.filter("http").entry("e1", tcp=8080)
config.contract("web-api").subject("s1").bind(filter="http")
config.push(aci)
Five relation classes and their target-prop spellings become two bind()s
and a verb; tcp=8080 compiles to etherT/prot/dFromPort/dToPort.
4 — A filtered query¶
bds = moDir.lookupByClass(
"fvBD", propFilter='and(eq(fvBD.arpFlood, "no"))'
)
bds = aci.query("fvBD").where(arpFlood="no").fetch()
Same wire attribute names (that is the APIC’s language), no hand-quoted
filter strings — and and_ / or_ / gt expressions compose when kwargs
run out (Observing the fabric).
5 — Everything under a subtree¶
from cobra.mit.request import DnQuery
dq = DnQuery('uni/tn-acme')
dq.queryTarget = 'subtree'
dq.classFilter = 'fvBD'
bds = moDir.query(dq)
bds = aci.tenant("acme").query("fvBD").fetch()
assert [bd.name for bd in bds] == ["web"]
6 — Large result sets¶
from cobra.mit.request import ClassQuery
cq = ClassQuery('fvCEp')
cq.pageSize = 1000
cq.page = 0
endpoints = []
while True:
page = moDir.query(cq)
endpoints.extend(page)
if len(page) < 1000:
break
cq.page += 1
for endpoint in aci.query("fvCEp").stream():
... # pages fetched transparently as you iterate
7 — Dry run and failure modes¶
cobra’s write path has one verb and one feedback channel — commit, then read the APIC’s answer:
try:
moDir.commit(req)
except Exception as ex: # error arrives from the APIC, after the POST
print(ex)
probe below is a fresh design carrying a deliberate typo; config is the
design from task 2, already pushed to the fabric.
from niwaki.exceptions import DesignError
probe = tenant("acme")
probe.vrf("prod")
probe.bd("web").bind(vrf="prdo") # typo
try:
probe.to_payload() # fails offline, before any request
except DesignError as exc:
print(exc) # …Did you mean 'prod'?
# config: the task-2 design, already on the fabric
plan = config.push(aci, mode="plan") # and the dry run is first-class
assert plan.has_changes is False
8 — Concurrent reads¶
cobra predates asyncio — fan-out means threads around a shared session. The niwaki mirror is first-class (Async patterns):
import asyncio
from niwaki import AsyncNiwaki
async def snapshot() -> None:
async with AsyncNiwaki("https://apic.example.com", "admin", "secret") as aci:
tenants, bds, faults = await aci.gather(
aci.query("fvTenant").fetch(),
aci.query("fvBD").fetch(),
aci.query("faultInst").fetch(),
)
print(len(tenants), len(bds), len(faults))
asyncio.run(snapshot())
The scoreboard is not one-sided everywhere: cobra guarantees write parity with your exact firmware and carries Cisco’s official support — when you need those, cobra remains the reference (Why niwaki — a comparison with cobra). For configuration as code — reviewable, diffable, converging, typed — the eight tasks above are the argument.