Skip to main content

napalm-fortigate

A NAPALM driver for Fortinet FortiOS, built on the FortiOS REST API rather than SSH screen-scraping.

Status: beta. All twenty-six NAPALM getters are accounted for -- twenty implemented against real hardware, six refused with a documented reason -- and the configuration lifecycle is complete: merge, replace, commit, commit-confirm and rollback. Nothing here reports data it has not actually seen.

Why this exists

There are three prior attempts at a NAPALM FortiOS driver. All are abandoned and none installs on a current Python:

project last commit problem
napalm-automation-community/napalm-fortios Apr 2022 last PyPI release 2018; depends on pyFG, which needs pip.req (removed in pip 10, 2018); its tests still import napalm_base, the pre-2.0 package name, so the suite has not run in years
n3s0/napalm-fortios + n3s0/pyfg Apr 2025 packaging fixed and it installs, but pyfg assigns self.host = host where the parameter is hostname, so constructing the client raises NameError immediately
glennake/napalm-fortinet Aug 2022 best foundation of the three (netmiko, no pyFG, correct imports) but abandoned mid-build; its test harness has never worked, since open() overwrites the test double with a real SSH connection

The common thread is SSH. Every one of them screen-scrapes a CLI, which means a parser per command, a dependency on terminal pagination behaviour, and -- on any unit with FortiToken enforced -- an authentication problem NAPALM cannot solve, because it has nowhere to get a TOTP code from.

REST avoids all of that. An API token is not subject to 2FA, responses are JSON, and there is no pager to disable. It also removes the need for the config system console / set output standard write that netmiko performs on every connect, which is a configuration change on a production firewall.

Why "fortigate" and not "fortios"

NAPALM names drivers after the operating system -- ios, junos, eos, nxos, panos -- so fortios would be the conventional choice, and it is what most people would guess.

It was not used, for two reasons. The name is claimed on PyPI by the abandoned 2018 package and used by three GitHub repositories, none of them maintained, so it carries confusion without carrying any compatibility worth having: the old driver has not been installable since 2018, so nobody has a working get_network_driver("fortios") to stay compatible with. And fortigate is more honest about scope, since this driver targets FortiGate units and will not work against FortiSwitch or FortiAnalyzer, which have different APIs.

The name to use is therefore fortigate:

get_network_driver("fortigate")

Inside napalm_fortigate/_rest.py the client is still called FortiOSClient, because the API it speaks to is genuinely named the FortiOS REST API, and because that module is the piece most likely to be extracted one day.

Requirements

  • Python 3.10+
  • napalm >= 5.0.0
  • requests (already a NAPALM dependency, so this driver adds nothing new)

Developed against FortiOS 7.4.x. Commit-confirm (revert_in) needs FortiOS 7.2.1 or later and a super_admin token; see Commit confirm for what has been measured on which firmware.

Install

pip install napalm-fortigate

To pin an exact release from the repository instead:

pip install "napalm-fortigate @ git+https://gitlab.com/ggiesen/napalm-fortigate.git@v0.2.1"

Setting up an API token

The driver authenticates with an API token, not a password. On the FortiGate, create a read-only access profile and an API user bound to it. Note that the built-in profiles are not read-only -- prof_admin grants read-write across every group -- so the profile has to be created:

config system accprofile
    edit "napalm-ro"
        set secfabgrp read
        set ftviewgrp read
        set authgrp   read
        set sysgrp    read
        set netgrp    read
        set loggrp    read
        set fwgrp     read
        set vpngrp    read
        set utmgrp    read
        set wifi      read
    next
end

config system api-user
    edit "napalm-ro"
        set accprofile "napalm-ro"
        set vdom "root"
        config trusthost
            edit 1
                set ipv4-trusthost <your-source-address>/32
            next
        end
    next
end

execute api-user generate-key napalm-ro

generate-key prints the token once. A trusthost is not optional in practice: without one the token is usable from anywhere that can reach the admin port. Be aware that pinning to a single dynamic address will break the driver the day it changes.

For configuration management the profile needs write access, so create a second API user rather than loosening the read-only one.

Usage

from napalm import get_network_driver

driver = get_network_driver("fortigate")
device = driver(
    hostname="192.0.2.1",
    username="",              # unused; the token identifies the caller
    password="",
    optional_args={
        "api_token": "<token>",
        "port": 8443,         # check admin-sport, it is often moved
        "ssl_verify": False,  # FortiGates ship a self-signed certificate
    },
)

device.open()
print(device.get_facts())
device.close()

ssl_verify defaults to True. Turning it off should be a deliberate choice; pointing it at a CA bundle path is better where you can.

Coverage

Verified against a FortiGate 60F on FortiOS 7.4.12.

getter source
get_facts monitor/system/status, monitor/system/interface, cmdb/system/dns
get_interfaces monitor/system/interface + cmdb/system/interface
get_interfaces_ip monitor/system/interface + cmdb/system/interface
get_interfaces_counters monitor/system/interface
get_arp_table monitor/network/arp
get_config monitor/system/config/backup -- see the privilege note below
get_environment monitor/system/resource/usage, monitor/system/sensor-info
get_optics monitor/system/interface/transceivers
get_lldp_neighbors monitor/network/lldp/neighbors
get_lldp_neighbors_detail monitor/network/lldp/neighbors
get_bgp_neighbors monitor/router/bgp/neighbors + cmdb/router/bgp
get_route_to monitor/router/ipv4, monitor/router/ipv6
get_ntp_servers cmdb/system/ntp
get_ntp_stats monitor/system/ntp/status
get_firewall_policies monitor/firewall/policy + cmdb/firewall/policy
get_bgp_config cmdb/router/bgp
get_bgp_neighbors_detail monitor/router/bgp/neighbors + paths + cmdb/router/bgp
get_network_instances cmdb/system/vdom + cmdb/system/interface
get_snmp_information cmdb/system.snmp/sysinfo + community
get_vlans cmdb/system/interface

All twenty-six getters are now either implemented or refused with a reason.

Two API quirks worth knowing if you extend this. SNMP is namespaced under system.snmp, with a dot -- cmdb/system/snmp/sysinfo returns HTTP 400 and cmdb/system.snmp/sysinfo works. And router/bgp/paths names the neighbour each path was learned_from, which is the only way to get a per-peer prefix count: the neighbors-statistics and paths-statistics endpoints return device-wide totals only.

Where FortiOS cannot answer

Several fields are -1 or empty by necessity, not by omission. They are worth knowing before building anything on top:

field why
get_facts -> uptime not reported anywhere in the REST tree
get_environment -> memory given as a percentage, with no total published to convert against
get_bgp_neighbors -> sent_prefixes, uptime not published at all; received/accepted counts are derived from router/bgp/paths
get_bgp_neighbors_detail -> message counts, negotiated timers, flap count, neighbour router ID not published; NAPALM's model describes far more than FortiOS reports
get_network_instances -> route_distinguisher FortiOS attaches no RD to a VDOM
get_route_to -> age routes carry an install date, not an age, and no reference clock is exposed
get_interfaces -> last_flapped FortiOS records no interface flap timestamp
get_interfaces_counters -> discards, multicast, broadcast totals only; no per-cast breakdown
get_lldp_neighbors_detail -> capabilities not advertised in the LLDP response
get_ntp_stats -> referenceid, when, hostpoll no FortiOS equivalent

Four things worth knowing, all of which only became apparent against real hardware:

uptime is always -1. FortiOS 7.4 does not report system uptime anywhere in the REST monitor tree. Not system/status, not system/resource/usage, not system/performance/status; the only uptime field in Fortinet's entire Ansible module catalogue belongs to HA configuration. -1 is NAPALM's convention for a fact the platform cannot supply, and is better than deriving a plausible number from something else.

get_config needs more than a read-only token. monitor/system/config/backup answers 403 to an access profile with read on every group, because a config backup contains secrets. It needs write access to the system group. The rest of the getters are happy read-only.

Some endpoints simply do not exist on smaller units. monitor/system/sensor-info and monitor/system/interface/transceivers both return 404 on a 60F, which is fanless and has no optical ports. The driver treats those as "no sensors" and "no optics" rather than as errors, but code that assumes a documented endpoint is present will break on the low end of the range.

monitor/system/resource/usage is enormous by default. It returns every historical sample and ran to 168KB on an idle unit. Always pass an interval.

Endpoint paths come from Fortinet's own fortinet.fortios Ansible collection, which ships a machine-readable catalogue of the REST API. Parsing is written against captured fixtures, not against those docs, so a getter only lands once its response has been seen.

Deliberately not implemented

These raise NotImplementedError with an explanation, and that is not expected to change.

getter why
get_mac_address_table The only MAC-table endpoints are under switch-controller, which covers managed FortiSwitches. A FortiGate's own internal switch is not exposed. Would need diagnose netlink brctl over SSH.
get_ipv6_neighbors_table No NDP endpoint exists. monitor/router/ipv6 returns routes, not neighbours. Would need diagnose ipv6 neighbor-cache list over SSH.
get_ntp_peers A FortiGate is an NTP client. Reporting its configured servers as peers would be wrong.
get_probes_config system/link-monitor is a failover health check, not an RPM/IP SLA probe.
get_probes_results As above.
ping, traceroute, cli The REST API has no endpoint that runs a command and returns its output. All 580 selectors in Fortinet's own module catalogue contain nothing of the sort, and monitor/network/ping, monitor/system/ping, the traceroute equivalents and monitor/system/exec all answer 404 on 7.4.12. monitor/system/config-script/upload does run CLI, but replies with a status envelope only -- a script of get system status returns success and no output -- so it is a configuration applicator, not a command runner.
get_users cmdb/system/admin returns an empty list to an API-token caller no matter what the access profile grants -- verified with read-write on every group, and with the vdom and global scopes tried explicitly, while the same device lists two administrators over SSH. Returning {} would be reporting something known to be false, since every FortiGate has at least one administrator.

Reaching the first two would mean a second credential -- an API token cannot open an SSH session -- plus an admin account exempt from FortiToken. That is a meaningful security cost for two getters, so the driver says no clearly instead of half-doing it.

VDOMs

Pass optional_args={"vdom": "..."} and every request carries that VDOM as a query parameter. Verified on a FortiGate-VM running 7.4.12 with vdom-mode set to multi-vdom:

  • the parameter is attached to every cmdb and monitor request
  • all eighteen getters that unit supports return correctly when scoped to a named VDOM
  • a VDOM that does not exist is rejected with HTTP 424 rather than silently ignored, so a typo fails loudly instead of quietly returning another VDOM's data

What has not been tested is separation between two VDOMs. The permanent evaluation licence will not allow a second one -- creating it fails with Command fail. Return code -4 (reached the maximum number of entries), despite get system status advertising "Max number of virtual domains: 2", which evidently counts internal VDOMs. Confirming that a getter scoped to VDOM A cannot see VDOM B's data needs a licensed multi-VDOM device.

Commit-confirm works on single- and multi-VDOM units; see the commit-confirm notes on VDOMs for the one difference.

Configuration management

FortiOS has no candidate configuration. Neither does Cisco IOS, and NAPALM's IOS driver has supported the full lifecycle for years by staging the candidate and applying it at commit. This driver does the same, holding the candidate in the driver rather than on the device.

NAPALM method FortiOS
load_merge_candidate held in the driver; nothing is sent yet
compare_config returns the staged commands
commit_config config-revision/save, then config-script/upload, then config/save
discard_config drops the staged text
load_replace_candidate held in the driver; applied by commit_config via config/restore
rollback config/restore of the revision commit_config took
commit_config(revert_in=N) a revision, then a one-time automation stitch that restores it in N seconds, then config-script/upload; not saved -- see Commit confirm
confirm_commit deletes the stitch, which cancels it, then config/save
has_pending_commit whether the stitch exists on the unit and has not fired

config-script/upload executes the script as it uploads it. There is no upload-now-run-later for local scripts; the separate config-script/run endpoint is for scripts pulled from a remote script server. So the upload happens in commit_config, not in load_merge_candidate.

commit_config does not lie. This was the question worth answering before trusting any of it, and it was tested against hardware: a script that fails partway comes back as HTTP 500 with status: error and a cli_error field carrying the full CLI transcript, naming the command that broke. That transcript is included in the MergeConfigException. For example:

MergeConfigException: Config script failed; the device may be partially
configured. POST .../system/config-script/upload returned HTTP 500:
fortigate  config system global
fortigate (global)  set hostname "..."
fortigate (global)  end
fortigate  bogus-command
Unknown action 0

Note what that transcript shows: the valid commands ran before the bad one failed. A failed commit can leave the device partially configured. The revision taken at the start of commit_config is how you get back, and that revision is taken before anything is applied -- if it cannot be taken, the commit aborts rather than proceeding with no way back.

compare_config returns the commands to be applied, not a diff. FortiOS has no server-side comparison, and the candidate is a fragment of CLI rather than a full configuration, so diffing it against the running config would be misleading. This is the same position librouteros PR #383 takes, for the same reason.

load_replace_candidate and rollback both go through monitor/system/config/restore, which reboots the unit. Two things about them are worth knowing before use.

They need a super_admin token. An access profile with read-write on every group is not enough -- config/restore answers 403 to one. This is the same shape of surprise as config/backup refusing a read-only token, one level up. The driver says so in the exception rather than passing the bare 403 along.

The reboot is measured, not assumed. On a FortiGate-VM running 7.4.12 the endpoint returns immediately with {"restore_started": true, "config_restored": true}, the unit stops answering about eleven seconds later, and is back roughly forty-three seconds after the call. Hardware will be slower. Validated by setting a hostname, taking a revision, changing it, restoring, and confirming it came back.

rollback() restores the revision commit_config recorded, and refuses if no commit has run on that connection. Rolling back to whatever revision happens to be newest would be guessing at intent; list them with monitor/system/config-revision and restore one by hand if that is what you want.

Commit confirm

commit_config(revert_in=N) schedules its own undo on the unit before applying anything:

  1. It takes a configuration revision, found again by a nonce in its comment.
  2. It creates a one-time automation stitch, napalm-commit-confirm, whose CLI script runs execute restore config flash <revision> N seconds from now.
  3. It applies the change through config-script/upload and does not save.

confirm_commit() deletes the stitch, which cancels it, and saves. If nobody confirms in time, the stitch fires and the unit restores the revision, which reboots it. The revision predates the stitch, so nothing is left behind.

The stitch is the pending marker. has_pending_commit(), confirm_commit() and rollback() all read it from the unit, so they give the right answer from a fresh connection, a different host, or minutes later. cfg-save is never touched.

Requirements

requirement why if not met
FortiOS 7.2.1 or later trigger-frequency once arrived in 7.2.1 CommitConfirmException, nothing changed
super_admin token execute restore, and rollback FortiOS refuses the request
reboot-upon-config-restore enable (the default) with it disabled a restore does not take effect until the next reboot CommitConfirmException, nothing changed
monitor/system/automation-stitch/stats reports the trigger time, used to read the unit's clock and check the schedule CommitConfirmException, nothing applied
revert_in between 60 and 86400 triggers fire up to 30 seconds late; a day is an arbitrary sane limit CommitConfirmException, nothing changed

Time is the unit's, not the caller's. A one-time trigger is written in the unit's local time. The driver reads the unit's UTC time from the HTTP Date header, reads its UTC offset back through a harmless throwaway stitch (napalm-commit-confirm-clock), then checks the real schedule against what the unit reports before applying anything. Skew between the automation host and the unit does not matter, and a timezone or DST surprise refuses the commit instead of arming the wrong time. The window is still measured on the unit's clock, so an NTP step during it moves the restore; keep units on NTP.

What has been measured, and where

Measured with tools/probe_scheduled_restore.py on a FortiGate-VM and by hand on a FortiGate 40F. 7.6.x has not been tested at all.

behaviour 40F, 7.2.10 VM, 7.4.12
a one-time trigger fires once, at its time, not at creation yes (CLI) yes (REST)
deleting the stitch before its time cancels it yes (CLI) yes (REST)
a trigger time already past is refused yes not tested
the stitch, trigger and action can be created over cmdb REST not tested yes
the stitch restores the revision, then is gone not tested yes, single and multi VDOM
someone saves during the window: the restore still runs and cleans up not tested yes
the stats endpoint exists and gives the offset away not tested yes
a revision captures running, not saved, configuration not tested yes

Triggers fired between 2 and 28 seconds late across the VM runs, and 8 seconds late on the 40F. On the VM the unit stopped answering about ten seconds after the stitch fired and was back about a minute after that. Hardware will be slower.

execute restore config flash asks for no confirmation when run from a script: the transcript goes straight to Get config from local disk OK. File check OK. and reboots. Fortinet's own auto-restore KB article uses the same primitive, a stitch running a CLI restore, for the same purpose.

cfg-save modes

The mechanism does not depend on cfg-save, but what "saved" means does, and so do the side effects.

cfg-save during the window not confirmed confirm_commit
manual change and stitch applied, unsaved restore, reboot cancels, then saves everything staged on the unit
automatic change and stitch saved as they are applied restore, reboot cancels; the save is a formality
revert change and stitch unsaved, so the unit's own revert timer runs as well restore, reboot -- or, if the unit's own cfg-revert-timeout is shorter than revert_in, its own revert gets there first, which also undoes the change (expected, not measured) cancels, then saves, which also ends the unit's own revert window

All three are exercised by the integration suite on the VM. The scheduled restore itself was probed from manual.

Things to know, most of them about manual:

  • A restore is the whole revision. Anything changed on the unit after the commit took its revision is rolled back too -- by anyone, including a save made during the window.
  • Revisions capture running configuration, not saved. On a manual unit, changes that were staged but unsaved when the commit started are in the revision, so an unconfirmed restore makes them permanent.
  • Confirming saves. On a manual unit that includes anything else staged, such as the paging change SSH backup tools make on every login. A plain commit_config has always done the same.
  • Someone saving during the window saves the stitch too. It still fires, restores, and is gone afterwards, because the revision predates it. Nothing fired again in the following five minutes.
  • With revision-backup-on-logout enabled, FortiOS saves a revision of its own whenever an admin session that changed something logs out, backup tools included. That does not confuse the commit, which finds its own revision by its nonce rather than by being newest, and it neither confirms nor cancels anything: a revision is a snapshot, not a save. Changes made over REST with an API token produce no such revision -- measured with the setting enabled and admintimeout at a minute -- so the driver's own traffic does not consume revision slots. Two things follow for the ones other sessions do write.
  • A revision taken during the window holds the pending change, and the schedule. Revisions capture running configuration, so one written while a commit is pending contains the unconfirmed change and napalm-commit-confirm. Restoring that revision by hand later brings the change back with it. The schedule comes back inert: restored with a trigger time already in the past, the stitch was present but never fired, and the unit reported no next trigger for it.
  • A rotated-away revision fails loudly. The VM kept 20 revisions, dropping the oldest as new ones arrived; a 40F has less flash, so check yours. The commit's revision is the newest when it is taken, so losing it inside the window would take a burst of other revisions. If it does happen the restore fails, the stitch stays in place, and has_pending_commit raises rather than reporting the change as confirmed.
  • If the stitch has fired but is still present, the restore did not take effect. has_pending_commit and confirm_commit raise rather than answer, and rollback() restores the revision.
  • confirm_commit raises when nothing is pending, as NAPALM's EOS and IOS drivers do. After a restore nothing is pending either, and returning quietly would report a rolled-back change as confirmed. Check has_pending_commit first, and verify the change when it says False.
  • A second commit_config while one is pending raises CommitError.
  • revert_in cannot be combined with load_replace_candidate: config/restore saves and reboots, so there is nothing to undo.
  • Don't change the driver's own API key in a push with revert_in. A restore brings the old key back.
  • HA is untested.

VDOMs

execute restore only exists in the global context. On a multi-VDOM unit the script is wrapped in config global ... end; on a single-VDOM unit it runs as is. Both restored correctly on the VM. Without the wrapper, a multi-VDOM unit ran the script and silently did nothing, which is how this was found. The automation objects are global either way. Commit-confirm together with optional_args["vdom"] has not been tested.

Why not cfg-save revert, auto-script, or transactions

cfg-save revert is FortiOS's own safety net, and the first implementation used it: switch to revert mode without saving, apply, and let the unit reboot to its saved configuration unless confirmed. tools/probe_cfg_revert.py measured it on 7.4.12. It worked from manual and revert, and from automatic the timer never armed at all. It was dropped because revert mode turns any unsaved change into a reboot. SSH tools that disable paging do it with config system console / set output standard, which is a configuration change, so a backup run against a unit in revert mode reboots it -- every night, if backups are nightly. And a unit would be left in revert mode if someone else saved during the window, since that saves the switch too, which the driver cannot prevent.

system auto-script cannot delay anything. With start auto it runs the script the moment it is created, and repeat 1 means one run in total. Measured on a 40F running 7.2.10. A scheduled restore would restore immediately.

Workspace mode and REST transactions stage changes until commit and discard them on timeout. Nothing is live during the window, so there is nothing for a post-check to verify. That is atomicity, not commit-confirm -- worth having separately, since a config script that fails partway leaves earlier commands applied.

Development

The project uses uv.

uv sync
uv run pytest
uv run ruff format --check .
uv run ruff check .
uv run mypy napalm_fortigate

The unit tests run entirely against captured fixtures under test/unit/mocked_data/. No test touches a device.

See CONTRIBUTING.md for the conventions this project follows, particularly around capturing and sanitising fixtures.

Layout

napalm_fortigate/
    _rest.py     FortiOS REST client. Imports nothing from napalm.
    fortigate.py The driver. All NAPALM coupling lives here.
tools/
    boot-fortigate.sh           boots a disposable FortiGate for the integration job
    probe_scheduled_restore.py  measures the scheduled restore commit-confirm uses
    probe_revisions.py          measures how stored revisions behave around it
    probe_cfg_revert.py         measures cfg-save revert, the design it replaced
    _probe.py                   the probes' shared, scrubbing log output

_rest.py is kept free of NAPALM imports on purpose. If a standalone FortiOS REST library ever turns out to be worth having, it can be lifted out without modification. Until something other than this driver wants it, splitting the repository would only buy release coordination overhead -- which is roughly how pyFG came to strand the original driver.

Branching

  • master is the production branch and is protected.
  • Work happens on feature branches, merged via merge request.
  • The pipeline runs formatting, linting, type checking and unit tests on every push and merge request; all four must pass before merge.
  • Releases are cut from master as a vMAJOR.MINOR.PATCH tag. The publish job builds and uploads to PyPI with Trusted Publishing (OIDC), so no API token is stored anywhere, and it is when: manual: tagging never publishes on its own.

Changelog

See CHANGELOG.md.

Licence

Mozilla Public License 2.0. See LICENSE.

Metadata

Release files for napalm-fortigate 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for napalm-fortigate 0.2.1
File Size Uploaded Provenance Actions
napalm_fortigate-0.2.1.tar.gz 48.6 kB Without provenance Details for napalm_fortigate-0.2.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for napalm-fortigate 0.2.1
File Interpreter ABI Platform Actions
napalm_fortigate-0.2.1-py3-none-any.whl Python 3 none any Details for napalm_fortigate-0.2.1-py3-none-any.whl

Total release size: 88.2 kB

Release files / napalm_fortigate-0.2.1.tar.gz

Download URL napalm_fortigate-0.2.1.tar.gz
Size 48.6 kB
Tags Source
SHA-256 checksum
How to use checksums
355ff1f8bb3140ca5a08874546630e38ac7c7baa3699324b10caa0c3486de986
BLAKE2b-256 checksum
How to use checksums
ee795ea90b02f13be06122c7746e8e9c5c192dc931fe4c1117910d80f26e9eee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / napalm_fortigate-0.2.1-py3-none-any.whl

Download URL napalm_fortigate-0.2.1-py3-none-any.whl
Size 39.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
14ad054ada3481677fcd8d0c7bd166cb03e9c796764825a3ebf1ae5c0a776514
BLAKE2b-256 checksum
How to use checksums
1036a887b3bdc30daff06fb8732eef21e0acc1aef6fcb00dceda48b73b440103
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.14

Security for napalm-fortigate 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page