Skip to main content

Python client library for the Hudu API

Project description

hudu-magic

A tiny, enum-driven, class-based Python API client for Hudu.

  • Minimal dependencies (requests)
  • Generated from OpenAPI
  • Low Maintenance
  • Designed for clarity and maintainability

PyWheels

PyPi


Quick Start

from hudu_magic import HuduClient

client = HuduClient(
    api_key="your_api_key",
    instance_url="https://yourinstance.huducloud.com"
)

company = client.companies.create(name="Test Company")

# Use a real asset_layout_id from your Hudu instance (e.g. from client.asset_layouts.list()).
asset = client.assets.create(
    company_id=company.id,
    name="Router",
    asset_layout_id=1,
)

asset.name = "Updated Router"
asset.save()

asset.delete()

Installation

Install package

pip install hudu-magic

Usage Info and Guide

There are several examples in the examples folder that might be helpful if you're just starting out

Core Concepts

Client

Handles auth, requests, pagination, wrapping.

client.assets.list()

Collections

Collection-level operations:

  • list()
  • get()
  • create()
  • delete()
  • archive()
  • unarchive()
assetsforcompany.save()
assetsforcompany.delete()
assetsforcompany.archive()

Models (HuduObject)

Instance-level operations:

  • save()
  • delete()
  • refresh()
  • relate_to()
  • list_photos()
  • list_uploads()
  • relate_to()
  • upload_to()
asset.save()
asset.delete()

Special Model Methods

Assets

someasset.add_public_photo("smile.png")
someasset.add_photo("dogslaughing.jpeg")

some objects can be attributed directly to or uploaded to assets

Companies

mycompany.list_assets()
mycompany.list_articles()
mycompany.list_passwords()
mycompany.list_procedures()
mycompany.list_websites()
mycompany.list_folders()
mycompany.list_password_folders()


mycompany.create_website()
mycompany.create_password()
mycompany.create_procedure()
mycompany.create_article()
mycompany.create_asset()

objects that require or can be attributed to a company often can be listed or created directly from a company object

Exports

starting a CSV or PDF export

newexport = client.exports.start(format="pdf", company_id=1, asset_layout_ids=[2],
    include_passwords= True,
    include_websites= True,
    include_articles= True,
    include_archived_articles= True,
    include_archived_passwords= True,
    include_archived_websites= True,
    include_archived_assets= True,
    )

client.Exports.new() is aliased to client.Exports.start()

csvexport = client.exports.start(format="csv",company_id=mycompany.id)
pdfexport = client.exports.start(format="pdf",company_id=mycompany.id)
Friendly defaults on create

the include_* options here default to true if not provided

the asset layout array defaults to all layouts found with HuduClient.asset_layouts.list are included.

Checking status of export

blocking-check on export status

ready = client.exports.wait_until_downloadable(newexport, interval=2.0, timeout=3600)
someexport.wait_until_downloadable(interval=5.0, timeout=600)

downloading exports

download = client.exports.download(newexport.id, "/home/myoutputfolder")
download = client.exports.download(otherexportobject) # download to current working dir
someexportobject.download() # download to current working dir
myexportobject.download("/home/myoutputfolder")

Asset Layouts and Fields

AssetLayout.to_create_payload() builds the same JSON body as normalize_layout_for_create on that layout’s underlying data. Prefer to_create_payload whenever you already have (or can wrap) an AssetLayout. Use normalize_layout_for_create only when you have a plain dict and no instance yet (for example after json.load).

Clone an existing layout (GET-shaped data normalized for POST):

mylayout = client.asset_layouts.get(2)
payload = mylayout.to_create_payload()
payload["name"] = "Updated New Layout"
newlayout = client.asset_layouts.create(payload=payload)
print(f"created new layout: {newlayout.name}")

Create a layout from scratch by wrapping a draft dict in AssetLayout, then calling to_create_payload. You can omit position on fields; contiguous positions and icon / include defaults are applied for you. For ListSelect fields you need a real list_id from your instance.

from hudu_magic.endpoints import HuduEndpoint
from hudu_magic.models import AssetLayout

draft = AssetLayout(
    client,
    HuduEndpoint.ASSET_LAYOUTS,
    {
        "name": "Docking stations",
        "fields": [
            {"label": "Asset tag", "field_type": "Text"},
            {"label": "Room", "field_type": "Text"},
        ],
    },
)
payload = draft.to_create_payload()
layout = client.asset_layouts.create(payload=payload)

Dict only, no AssetLayout yet (same payload shape as above):

from hudu_magic import normalize_layout_for_create

layout_dict = {"name": "Docking stations", "fields": [{"label": "x", "field_type": "Text"}]}
payload = normalize_layout_for_create(layout_dict)
# pass `payload` to client.asset_layouts.create(payload=payload) when you have a client

Layout Fields

Below are the valid layout field types (as of May11, 2026). These fields have different requirements and validation is intentionally somewhat loose for most of these, so that any error codes present will be reflected directly by Hudu itself. Not to worry, however, Hudu field validation does not change wildly.

  • Text
  • Number
  • CheckBox
  • Website
  • AssetTag
  • Email
  • Phone
  • Date
  • RichText
  • Heading
  • Password
  • ListSelect

Website fields (which require https:// prefix) are automagically handled Number fields must be an integer, but we safely coerce when possible from float, double, oer either as number-string ("1" or "44.7")

Procedures (processes) and tasks

The API and OpenAPI 2.41.0 use process / run wording; this library still exposes Procedure / procedure_tasks and the client.procedure / client.procedure_tasks aliases (client.process, client.tasks, etc.).

myprocedure.kick_off()
myprocedure.kickoff()
myprocedure.start()

myprocedure.is_run #bool property

companyprocedures = mycompany.list_procedures()
procedures = client.procedures.list()

myprocedure = client.procedures.create(payload={"name": "asdf", "company_id": 1})
myprocedure.add_task(name="newtask", auto_kickoff=True)

client.procedure_task.create(name="newtask", procedure_id=myprocedure.id)

# One procedure only — not on .list() results
proc = client.procedures.get(id=1)
proc.add_task(name="Step 1", auto_kickoff=True)

someprocedure.list_tasks()
someotherprocedure.tasks

sometask.assign_to(mypersonaluser)

Calling kick_off, kickoff, or start returns a new run (still a Procedure with is_run true). Runs share the same model as templates but behave differently for tasks.

Use is_run to tell a template from a run.

Creating tasks (POST /procedure_tasks) is for process (template) tasks only: supported body fields include name, description, procedure_id, position, optional, and parent_task_id. You cannot set assignees or run-only fields on create; kick off the process first, then set due_date, priority, and assigned_users on the run task via PUT /procedure_tasks/{id}, or use Users.assign_task (which updates assigned_users on the run task).

Updating a procedure/run (PUT /procedures/{id}) accepts name, description, and archived (company processes only for archiving). It no longer accepts moving a process between companies via company_id or legacy company_template on PATCH—use create/duplicate flows per Hudu’s API.

POST / PUT /procedures use a flat JSON body ({"name": "...", "company_id": ...}), not a nested procedure object—this matches Hudu 2.39.6+ and avoids 422 “Name can’t be blank” if the server ignored the old wrapper.

Paginated GET /procedures responses are normalized to a HuduCollection even when only one row is returned (so len() is a row count and for p in … yields Procedure objects, not attribute names). List payloads may use procedures or processes as the collection key.

Procedure.save() sends only those allowed fields so validation matches the spec.

Use Procedure.add_task(...) to create a template task (optional auto_kickoff=True after create). Run-only fields belong on the run task after kickoff.

Users

myuser.assign_task(thistask)
myotheruser.assign_task(client.procedure_tasks.get(56))

You can assign a run task from a Users instance or call task.assign_to(user); both use assigned_users on the run task.

Others

there are many other handy and helpful class methods and many more that are planned. Whenever possible, I'll update this section with specific examples.


Creating Objects

The create base method for all objects is simple. you can specify properties in either the payload object (standard dictionary) or as kwargs (just propertyname=value)

This means you can use either:

kwargs (recommended)

client.assets.create(name="Router", company_id=1, asset_layout_id=10)

dict payload

client.assets.create(payload={"name": "Router", "company_id": 1, "asset_layout_id": 10})

Updating Objects

asset.name = "New Name"
asset.save()

or

asset.update(name="New Name")

Relations

asset.relate_to(website)

or

client.relations.create(from_obj=asset, to_obj=website)

Uploads

asset.upload_to("file.zip")

uploads = asset.list_uploads()

Photos

asset.add_photo("image.png")

photos = asset.list_photos()

Generating builds for new Hudu versions or previous versions

  1. Place openapi spec file https://yoururl.huducloud.com/api-docs.json in project directory as hudu-openapiv1.json

  2. run python generate_endpoints.py after sourcing virtual environment (that has dev dependencies installed)

  3. run ./build.sh

todo: .\build.ps1

this is designed to be super simple so that subsequent releases can eventually just be automatically generated, tested, validated, and pushed to pypi.

Note on building and tests

  • Run tests with ./build.sh --test (or pytest from a dev environment with pip install -e ".[dev]").
  • Integration tests are skipped unless you set HUDU_RUN_INTEGRATION=1. With that set, copy testenv.example to testenv and fill in HUDU_TEST_API_KEY and HUDU_TEST_INSTANCE.

Error Handling, Additional Info / Help

If more information is needed, you can call this method on class members to get all associated info from hudu's API spec-

huduobject.help()

For resources such as client.assets, you can call:

client.assets.describe()

or for more verbose info:

client.assets.help()

if an object type or resource doesnt support a method call or payload param, you'll be notified of which one(s), if any, are invalid.

Advanced Use Possibilities

Multi-Client

You can instantiate two or more client objects, like above, to transfer data from, say, your dev instance to production. This hasn't been extensively tested expecially for objects dependent on companies (assets, passwordfolders)

client2.assets.create(
    **client1.assets.get(6).to_dict()
)

Philosophy

  • Simple > clever
  • Explicit > implicit
  • Thin wrapper over Hudu API

License

MIT


Versioning convention

PyPI releases use a library SemVer prefix and a numeric suffix derived from the Hudu OpenAPI spec used to generate HuduEndpoint and related code:

MAJOR.MINOR.HUDUSPECVERSION

  • MAJOR / MINOR — reserved for this Python package (breaking API changes, larger feature sets, and so on).

  • HUDUSPECVERSION — encodes the spec’s (major, minor, patch) as a single integer:

    hudu_spec_major * 1000 + hudu_spec_minor * 10 + hudu_spec_patch

    Example: OpenAPI 2.41.02 * 1000 + 41 * 10 + 0 = 2410 → package segment 0.1.2410 (with 0.1 as the current library prefix).

When Hudu publishes a new spec, regenerate and bump HUDUSPECVERSION accordingly. For Python-only fixes (same spec, no regeneration), prefer a PEP 440 suffix such as 0.1.2410.post1 so the encoded spec stays honest.

Spec used for the current release: Hudu OpenAPI 2.41.0 (as of 2026-04-06). The canonical package version is in pyproject.toml.

History

Hudu 2.41.0 Spec

  • v0.1.2410 - Apr 6, 2026; Initial Release

  • v0.2.2410 - Apr 7, 2026; added validation, differentiation for procedure-vs-run and task-vs-runtask, as well as some helpful class methods.

  • v0.3.2410 - Apr 21, 2026; Procedures POST/PUT send a flat JSON body (no procedure wrapper), Paginated lists always return HuduCollection (removed previous single-page len/iteration quirks); accept processes list key on GET; add Procedure.add_task. README Re-aligned with process/run task rules; Procedure.save/update/delete use PROCEDURES_ID and allowed PATCH fields; PROCEDURE_TASK_RUN_ONLY_FIELDS no longer lists removed user_id update key. BaseResource.create / update: optional payload (kwargs-only body fields supported); validate / allow_unknown_fields are not merged into JSON.

  • v0.4.2410.post1 - Added better support for exports, aliased create methods for exports to new() and start(). Added kind defaults to these and extended resource from BaseFileResource to allow for downloading. Lastly, added blocking method that until an export is ready for download. [PEP440 suffix noted for tag adjustment.]

  • v0.4.2411 - Generated Endpoints.py from new 2.41.1 spec, which is actually no different than previous release. For consistency and clarity, pushing new release tag, Fri, April 24th, 2026

  • v0.4.2412 - Generated Endpoints.py from 2.41.2 spec, Tues, April 28, 2026

  • v0.5.2412 - Including some internally-developed helpers / sane defaults for asset layouts and fields. Such helpers facilitate creating new layouts or moving layouts (and objects referenced by fields) more easily. FIELD_TYPES, ASSET_LAYOUT_FIELD_READ_ONLY_KEYS, ASSET_LAYOUT_POST_BODY_KEYS are for validation. normalize_layout_for_create facilitates isomorphism for entire layouts (as huduobject-layout or layout from dictionary). Furthermore, added some very basic and specific field validation for website fields and number fields, specifically.

  • v0.5.2420 - Generated Endpoints.py from 2.42.0 spec, Thurs, May 14, 2026

  • v0.5.2421 - Generated Endpoints.py from 2.42.1 hotfix, Thurs, May 20, 2026

  • v0.5.2430 - Generated Endpoints.py from 2.43.0 definitions, Wed, May 27, 2026

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hudu_magic-0.5.2430.tar.gz (59.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hudu_magic-0.5.2430-py3-none-any.whl (57.5 kB view details)

Uploaded Python 3

File details

Details for the file hudu_magic-0.5.2430.tar.gz.

File metadata

  • Download URL: hudu_magic-0.5.2430.tar.gz
  • Upload date:
  • Size: 59.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for hudu_magic-0.5.2430.tar.gz
Algorithm Hash digest
SHA256 e1f8c8c8d98dd0da1dd507a5c70c71c253b9c2f68622decfda32b5fc6ba6e931
MD5 54632e474c4464727164cb79d2b1f963
BLAKE2b-256 f7545b732ff0660c98ba2254d1fc36ba86c71f614bcaaedf8be4768d842f166d

See more details on using hashes here.

Provenance

The following attestation bundles were made for hudu_magic-0.5.2430.tar.gz:

Publisher: publish-pypi.yml on Hudu-Technologies-Inc/hudu-magic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hudu_magic-0.5.2430-py3-none-any.whl.

File metadata

  • Download URL: hudu_magic-0.5.2430-py3-none-any.whl
  • Upload date:
  • Size: 57.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for hudu_magic-0.5.2430-py3-none-any.whl
Algorithm Hash digest
SHA256 8b2ea84525b23e32385ffae1d263b4589a9f751dac7119a19b75e31a17318481
MD5 72adc22af1c25c5c03c0d6c8db62eec3
BLAKE2b-256 007fb71f1ace3c483669ef5fa2c8812507b6a9dd562c22b683d6025d4170e801

See more details on using hashes here.

Provenance

The following attestation bundles were made for hudu_magic-0.5.2430-py3-none-any.whl:

Publisher: publish-pypi.yml on Hudu-Technologies-Inc/hudu-magic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page