Skip to main content

Kardia Python API

A typed Python client library for the Kardia system (a Centrallix-based ERP/CRM used by DiscipleMakers). It wraps Kardia's REST interface with Python modules and objects, so you work with kardia.gl.getFund(...) and Fund(...) instead of hand-building URLs and JSON payloads.

Quickstart

Install (currently published to TestPyPI only — see Installation):

$ python -m pip install -i https://test.pypi.org/simple/ kardia

Connect with your Kardia server URL and credentials:

from kardia import Kardia

kardia = Kardia(kardia_url="https://your-kardia-server.com/apps/kardia", user="myuser", pw="mypassword")

Each Kardia domain is a lazily-constructed attribute on kardia — kardia.gl, kardia.donor, kardia.partner, etc. (see Modules for the full list). Reading data returns a KardiaResponse — a requests.Response subclass, so everything you'd normally do with a Response (.status_code, .json(), .content, ...) still works, plus a couple of convenience additions (.data, .ok_or_raise(), .osml — see API Module):

>>> res = kardia.gl.getFund("L1", "1000")
>>> res.status_code
200
>>> res.json()
{'a_fund': '1000', ...}
>>> res.data  # same as res.json(), but None instead of raising on an empty/non-JSON body
{'a_fund': '1000', ...}

Writing data follows a consistent pattern: build an *Object from kardia.objects, then pass it to the matching create*/update* method:

from kardia.objects.gl_objects import Fund

fund = Fund("1000", "L1", desc="General Fund")
res = kardia.gl.createFund(fund)
res.status_code  # 201 on success

Object constructors use Python-friendly names (desc, ledger_id) but serialize to Kardia's single-letter-prefixed attribute names (a_fund_desc, a_ledger_number) when sent as the request body — see How It Works for the full mapping rules (booleans, Enums, datetime, and Decimal money amounts).

Table of Contents

Installation


Published to TestPyPI (there is no PyPI release yet — make test-publish/make publish are the only publish targets):

$ python -m pip install -i https://test.pypi.org/simple/ kardia

Renamed from kardia_api. The distribution and top-level import were renamed to kardia (pip install kardia, from kardia import Kardia). Existing code that does import kardia_api keeps working unchanged — the old name is now a thin alias package pointing at the same classes — but new code should use kardia.

For local development against a clone of this repo, install editable with the test extras (responses, toml):

$ python -m pip install -e ".[test]"

Requires Python >= 3.6; the only runtime dependency is requests. The package ships a py.typed marker, so type checkers (mypy, pyright) and IDEs pick up its type hints automatically.

How It Works


The Kardia Client

kardia.Kardia(kardia_url, user, pw) is the top-level client. Each Kardia domain (crm, crm_config, designation, disb, donor, fundmanager, gift, gl, partner, report, app_info, files, config, acctconfig) is exposed as a lazily-instantiated attribute: the module class is only constructed (and its warm-up request only fires) the first time you access it, and the same instance is reused after that.

You can also reach endpoints not covered by a built-in module by writing your own APIModule subclass and registering it with kardia.register_module(...) — see Custom Modules.

Objects

Every request payload object subclasses APIObject (kardia/objects/api_object.py), which:

  • Stamps s_created_by / s_modified_by to the literal string "api" (not your connecting user — there is no "current user" concept baked into objects) and s_date_created / s_date_modified to the current timestamp, on every object you construct.
  • Provides serialize(), called internally by every create*/update* method to build the request body. It walks the object's attributes, keeps only those matching Centrallix's single-letter-prefix convention (^\w_.* — e.g. p_partner_key, a_gl_ledger_number, s_created_by), and converts as it goes:
    • bool → 1/0
    • Enum → .value
    • datetime → {"year", "month", "day", "hour", "minute", "second"}
    • Decimal → {"wholepart": ..., "fractionpart": ...} (fraction out of 10000, i.e. 4 decimal digits; negative amounts borrow from the whole part so the fraction stays positive, e.g. Decimal("-1.25") → {"wholepart": -2, "fractionpart": 7500})
  • Enforces required fields via required_attrs + checkValid() (called at the start of serialize()): if an object subclass sets self.required_attrs = ["a_ledger_number", ...] and any of those are falsy when you call create*, you get a plain Exception before any request is sent.

Modules and Endpoints

Every module subclasses APIModule (kardia/modules/api_module.py), which owns the requests.Session, HTTP Basic auth, and endpoint construction. Each module defines one or more Enum classes whose members are URL path segments, each carrying a PARENT pointer up to its parent Enum (or None at the root). _getEndpoint/_postEndpoint/_patchEndpoint walk that PARENT chain and zip each segment with a positional value you pass in.

For example, donor.py defines this chain:

from .api_module import RootEndpoint

class Endpoints(Enum):
    PARENT = RootEndpoint.ID
    YEARS = "Years"
    ...

class YearEndpoints(Enum):
    PARENT = Endpoints.YEARS
    GIFTS = "Gifts"

and Donor.getYearGift:

def getYearGift(self, partner_id: str, year_id: str, gift_number: int) -> Response:
    return self._getEndpoint(YearEndpoints.GIFTS, partner_id, year_id, gift_number)

Walking YearEndpoints.GIFTS → Endpoints.YEARS → RootEndpoint.ID root-first, and zipping with (partner_id, year_id, gift_number), produces:

GET {kardia_url}/api/donor/{partner_id}/Years/{year_id}/Gifts/{gift_number}?cx__mode=rest&cx__res_format=attrs

Module methods call self._getEndpoint(...)/self._postEndpoint(...)/self._patchEndpoint(...) with the target Enum member and the values needed to fill the chain; they don't hand-build URL strings.

URI params (mode, res_type, res_format, res_attrs, res_levels — see Centrallix REST Interface URI Parameters) are usually inferred automatically; see URI Params for the three ways to control them explicitly.

Authentication

Every request is sent with HTTP Basic auth (user/pw) over a shared requests.Session. POST/PATCH requests additionally need an akey (a Centrallix app auth token) — it's fetched lazily via a side request the first time you call a create*/update* method on a module instance, then cached on that instance for its lifetime (it is not automatically refreshed if it becomes stale — construct a fresh module instance, or re-access kardia.<module>'s backing instance, if that happens). A 401 response triggers exactly one automatic retry of the same request.

Known Quirks

A few behaviors are intentional (or at least stable/relied-upon) rather than bugs to "fix" reflexively if you're extending this library:

  • s_created_by is always "api". See Objects above — don't rely on it reflecting the connecting user.

Two verified source bugs in crm_config.py, documented here (not silently perpetuated as correct behavior) rather than fixed, since this is a docs pass:

  • getTrackCollaborator(track_name, step_name) and getTrackCollaborators(track_name) actually query the Steps endpoint, not a Collaborators endpoint — likely a copy/paste mistake.
  • getDocumentTypes() actually queries DataItemTypes, not DocumentTypes (getDocumentType(doc_type_id), the singular form, is correct).

Kardia Objects


APIObject

Parent class for all Kardia objects — see Objects above for what it does.

CRM Config

from kardia.objects import crm_config_objects as cco

Track

cco.Track(name: str, description: str, status: Statuses = Statuses.ACTIVE, t_id: int = None, color: str = None)

Step

cco.Step(track_id: int, name: str, description: str, sequence: int = 1, s_id: int = None)

Track Collaborator

cco.TrackCollaborator(track_id: int, partner_id: str, collab_type_id: int, comments=None)

Step Collaborator (subclasses TrackCollaborator, adds step_id; used by createStepCollaborator)

cco.StepCollaborator(track_id: int, step_id: int, partner_id: str, collab_type_id: int, comments)

Step Requirement (used by createSetRequirement)

cco.StepRequirement(track_id: int, step_id: int, req_id: int, name: str, whom: Whom, waivable: bool, sequence: int = 1, active: bool = True, due_days_from_step: int = None, due_days_from_req: int = None, due_days_from_req_id: int = None, req_doc_type_id: int = None)

Designation

from kardia.objects import designation_objects as do

Funding Target

do.FundingTarget(ledger_id: str, fund_id: str, target_id: int, target_desc: str, review: str = None, amount: str = "0.00", interval: int = Intervals.MONTHLY, start_date: datetime = datetime.today(), end_date: datetime = None)

Admin Fee

do.AdminFee(fund_id: str, ledger_id: str, fee_type: str, subtype: str = None, percentage: float = None)

Receipting

do.Receipting(fund_id: str, ledger_id: str, receiptable: bool = True, disposition: str = None)

Receipting Account

do.ReceiptingAccount(fund_id: str, ledger_id: str, account: str, non_tax_deductible: bool = False, default: bool = False, receipt_comment: str = None)

Disbursement

from kardia.objects import disb_objects as do

Line Item

do.LineItem(ledger: str = None, period: str = None, batch: int = None, disb_id: int = None, line_item_no: int = None, effective_date: datetime = datetime.today(), cash_account: str = None, amount: str = "0.00", fund: str = None, account: str = None, payee: str = None, check_no: str = None, posted: bool = False, gl_posted: bool = False, voided: bool = False, approved_by: str = None, approved_date: datetime = None, paid_by: str = None, paid_date: datetime = None, reconciled: bool = False, comment: str = "")

Donor

from kardia.objects import donor_objects as do

Settings

do.Settings(key: str, ledger: str, account_code: str = None, account_with_donor: str = None, allow_contributions: bool = True, location_id: int = None, contact_id: int = None, org_name_first: bool = True, receipts: str = None, is_daf: bool = False)

Fund Manager

from kardia.objects import fundmanager_objects as fo

Settings

fo.Settings(ledger_id: str, fund: str, partner_key: str, start_date: datetime = None, end_date: datetime = None)

Gift

from kardia.objects import gift_objects as go

Gift

go.Gift(ledger: str = None, batch: int = None, gift: int = None, period: str = None, amount: Decimal = Decimal("0.00"), gift_type: GiftTypes = GiftTypes.CHECK, foreign_amount: Decimal = None, foreign_currency: str = None, foreign_currency_exchange_rate: float = None, foreign_currency_date: datetime = None, posted: bool = False, posted_to_gl: bool = False, receipt_number: str = None, partner: str = None, ack_partner: str = None, pass_partner: str = None, receipt_sent: bool = False, ack_receipt_sent: bool = False, receipt_desired: ReceiptPreferences = ReceiptPreferences.IMMEDIATE, ack_receipt_desired: ReceiptPreferences = None, first_gift: bool = False, goods_provided: Decimal = Decimal("0.00"), received_date: datetime = None, postmark_date: datetime = None, receipt_sent_date: datetime = None, ack_receipt_sent_date: datetime = None, comment: str = None, item_objects = [])

Gift Item

go.GiftItem(ledger: str = None, batch: int = None, gift: int = None, split: int = None, period: str = None, fund: str = None, account_code: str = None, amount: Decimal = Decimal("0.00"), foreign_amount: Decimal = None, foreign_currency: str = None, foreign_currency_exchange_rate: float = None, foreign_currency_date: datetime = None, document_id: str = None, account_hash: str = None, check_front_image: str = None, check_back_image: str = None, posted: bool = False, posted_to_gl: bool = False, admin_fee: float = None, admin_fee_subtype: str = None, calc_admin_fee: float = None, calc_admin_fee_type: str = None, calc_admin_fee_subtype: str = None, recip_partner: str = None, confidential: bool = False, non_tax_deductible: bool = False, motivational_code: str = None, intent_code: str = None, comment: str = None, eg_source_key: str = None, donor_partner: str = None, ack_partner: str = None, pass_partner: str = None, receipt_number: str = None, received_date: datetime = None, postmark_date: datetime = None, gift_type: GiftTypes = None)

EG Gift Import — represents one row of an external-giving-platform (e.g. online donation processor) import, consumed by createEGGiftImport. It has ~70 fields covering donor identity, address, payment/currency details, and fund/account mapping confidence scores; see objects/gift_objects.py's EGGiftImport class for the full field list rather than duplicating it here. Construct it with keyword arguments, e.g.:

go.EGGiftImport(ledger="L1", gift="uuid-1", designation="uuid-2", line_item=1, transaction="uuid-3",
                 donor="uuid-4", status="new", processor="stripe", donor_name="Jane Donor",
                 amount=Decimal("50.00"), interval="one_time", date=datetime.now(), designation_name="General Fund")

GL

from kardia.objects import gl_objects as glo

Account

glo.Account(code: str, ledger_id: str, desc: str, parent_code: str = None, acc_type: AccountTypes = AccountTypes.ASSET, acc_class: str = "GEN", reporting_level: int = 1, banking_key: str = None, contra: bool = False, posting: bool = True, inverted: bool = False, intrafund_xfer: bool = False, interfund_xfer: bool = False, comment: str = None, legacy_code: str = None, category: str = None)

Fund

glo.Fund(fund_id: str, ledger_id: str, parent_id: str = None, bal_fund_id: str = None, fund_class: str = None, reporting_level: int = 1, posting: bool = True, external: bool = False, balancing=True, restricted_type: RestrictedTypes = RestrictedTypes.NOT_RESTRICTED, desc: str = None, comments: str = None, legacy_code: str = None)

Period

glo.Period(period_id: str, ledger_id: str, start_date: datetime, end_date: datetime, parent_id: str = None, status: PeriodStatuses = PeriodStatuses.NEVER_OPENED, summary: bool = False, first_opened: datetime = None, last_closed: datetime = None, archived: datetime = None, desc: str = "", comment: str = "")

Year

glo.Year(period_id: str, ledger_id: str, start_date: datetime, end_date: datetime, desc: str = "", comment: str = "")

Batch

glo.Batch(ledger_id: str, period_id: str, batch_id: int = None, desc: str = "", origin: str = "GL", date: datetime = None)

Transaction

glo.Transaction(ledger_id: str = None, period_id: str = None, batch_id: int = None, journal_id: int = None, date: datetime = datetime.today(), fund: str = None, account_category: AccountCategories = AccountCategories.EXPENSES,account_code: str = None, amount: str = "0.00", isPosted: bool = True, comment: str = None)

Analysis Attribute

glo.AnalysisAttribute(attr_code: str, ledger_id: str, desc: str, fund_enable: bool, account_enable: bool)

Analysis Attribute Value

glo.AnalysisAttributeValue(attr_code: str, ledger_id: str, value: str, desc: str = None)

Account Analysis Attribute

glo.AccountAnalysisAttribute(attr_code: str, ledger_id: str, account_code: str, value: str = None)

Fund Analysis Attribute

glo.FundAnalysisAttribute(attr_code: str, ledger_id: str, fund: str, value: str = None)

Partner

from kardia.objects import partner_objects as po

Partner

po.Partner(key: str, office: str, parent_key: str = None, p_class: str = "IND", status_code: StatusCodes = StatusCodes.ACTIVE, status_date: datetime = None, p_title: str = None, first_name: str = None, preferred_name: str = None, last_name: str = None, last_name_first: bool = False, localized_name: str = None, suffix: str = None, org_name: str = None, gender: str = None, language: str = None, acquisition: str = None, comments: str = None, record_status_code: RecordStatusCodes = RecordStatusCodes.ACTIVE, no_mail_reason: str = None, no_solicitations: bool = False, no_mail: bool = False, fund: str = None, best_contact: str = None, merged_with: str = None, legacy_key_1: str = None, legacy_key_2: str = None, legacy_key_3: str = None, staff_object = None, address_objects = None, contact_info_objects = None)

Address

po.Address(key: str, location_id: int, revision_id: int, location_type: str = None, date_effective: datetime = None, date_good_until: datetime = None, purge_date: datetime = None, in_care_of: str = None, addr1: str = None, addr2: str = None, addr3: str = None, city: str = None, state_province: str = None, country: str = None, postal_code: str = None, postal_mode: str = None, bulk_postal_code: str = None, certified_date: datetime = None, postal_status: str = None, postal_barcode: str = None, record_status_code: str = RecordStatusCodes.ACTIVE, comments: str = None):

Contact Info

po.ContactInfo(key: str, contact_id: int, contact_type: str, location_id: str = None, phone_country: str = None, phone_area_city: str = None, contact_data: str = None, record_status_code: str = RecordStatusCodes.ACTIVE, comments: str = None)

Staff

po.Staff(key: str, is_staff: bool = None, kardia_login: str = None, kardiaweb_login: str = None, preferred_email: int = None, preferred_location: int = None)

Report

from kardia.objects import report_objects as ro

Sched Report Status — used by Report.updateSchedReportStatus

ro.SchedReportStatus(sent_status: SchedStatusTypes, sent_error: str, sent_date: datetime, generated_report_path: str)

Sched Report Batch Status — used by Report.updateSchedReportBatchStatus

ro.SchedReportBatchStatus(sent_status: SchedStatusTypes = None, sent_by: str = None)

SchedStatusTypes values: NOT_SENT, SENT, TEMPORARY_ERROR, INVALID_EMAIL_ERROR, SKIPPED, FAILURE_OTHER_ERROR.

Modules


API Module

Parent class for all Kardia API modules — see Modules and Endpoints above.

Every module method returns a KardiaResponse, a thin requests.Response subclass with four additions:

  • res.data — res.json(), but returns None instead of raising on an empty or non-JSON body.
  • res.ok_or_raise(message=None) — raises KardiaAPIError (carrying .response/.status_code) if the response isn't a 2xx, otherwise returns res so it's chainable:
res = kardia.gl.createFund(fund).ok_or_raise("Failed to create fund")
  • res.get(name, default=None) — shortcut for res.osml.get(name, default) (default if the body isn't a JSON object).
  • res.osml — a KardiaOSML parse of the response body (None if the body isn't a JSON object), understanding both of Centrallix's OSML JSON shapes: plain Basic-format values, and Full-format (cx__res_attrs=full) {"a","e","v","t","h"} attribute metadata. Money/Datetime values are decoded to Decimal/datetime either way. KardiaOSML exposes:
    • .attrs (alias .element) — dict[str, OSMLAttribute], each with .value (decoded), .raw_value, .type, .error, .hints.
    • .get(name, default=None) / osml[name] — the decoded value for an attribute, or the child KardiaOSML if name names a collection member instead (e.g. osml.get("Gifts")).
    • .children (alias .collection) — dict[str, KardiaOSML] for a Full-format collection's @id-keyed child elements.
    • .to_dict() — a flat {name: value} dict of the decoded attrs.
    • Iterating a KardiaOSML (for member in osml) yields its collection members (.children's values) — e.g. for gift in res.osml.get("Gifts"): ....
  • Iterating a KardiaResponse directly (for member in res) is a shortcut for iterating res.osml — e.g. for year in kardia.gl.getYears("DM"): print(year). Raises TypeError if the body isn't a JSON object (e.g. a binary file download via kardia.files.getFile) — use .content/.raw for those instead.

res_type=both responses ({"cx__element": {...}, "cx__collection": {...}}) are transparently unwrapped: the element's attrs and the collection's children both land in .attrs/.children (aka .element/.collection) on the same KardiaOSML instance.

res = kardia.gl.getFund("L1", "1000", res_attrs="full")
res.osml.get("a_fund")  # decoded value, same as res.osml["a_fund"]
res.osml.attrs["a_fund"].type  # e.g. "string"

Account Config

>>> kardia.acctconfig

Methods

  • getConfigNames(ledger_id: str)
  • getConfigValue(ledger_id: str, config_name: str)

App Info

>>> kardia.app_info

Methods

  • getAppInfo()

Config

>>> kardia.config

Methods

  • getConfig()

CRM

>>> kardia.crm

Missionary and Supporter functionality lives here rather than in separate modules — the missionary.py/partnersearch.py/supporter.py files that appear in modules/ are empty stubs and aren't wired into kardia. There is no partner-search endpoint anywhere in this library.

Methods

  • getTracks()
  • getTrack(track: str)
  • getTrackSteps(track: str)
  • getTrackStep(track: str, step: str)
  • getStepCollaborators(track: str, step: str)
  • getStepCollaborator(track: str, step: str, track_id: int, step_id: int, collaborator_id: str)
  • getTrackCollaborators(track: str)
  • getTrackCollaborator(track: str, track_id: int, collaborator_id: str)
  • getTagTypes()
  • getTagType(tag_type_id: str)
  • getContactHistTypes()
  • getContactHistType(contact_history_type_id: str)
  • getCollaboratorTypes()
  • getCollaboratorType(collab_type_id: str)
  • getTodoTypes()
  • getTodoType(todo_type_id: str)
  • getWorkflowTypes()
  • getDataItemTypes()
  • getDataItemType(di_type_id: str)
  • getDocumentTypes()
  • getDocumentType(doc_type_id: str)
  • getCountries()
  • getCountry(country: str)
  • getTextExpansions()
  • getTextExpansion(expansion_id)
  • getPartners()
  • getPartner(partner_id)
  • getPartnerTags(partner_id: str)
  • getPartnerTag(partner_id: str, tag: str)
  • getPartnerDocuments(partner_id: str)
  • getPartnerDocument(partner_id: str, document: str)
  • getPartnerContactHistory(partner_id: str, contact_history_id: str = "")
  • getPartnerContactAutorecord(partner_id: str, contact_history_id: str = "")
  • getPartnerTracks(partner_id: str)
  • getPartnerTrack(partner_id: str, track: str)
  • getSupporters()
  • getSupporter(supporter_id: str)
  • getSupporterPrayers(supporter_id: str)
  • getSupporterPrayer(supporter_id: str, prayer_id: str)
  • getSupporterComments(supporter_id: str)
  • getSupporterComment(supporter_id: str, comment_id: str)
  • getSupporterMissionaries(supporter_id: str)
  • getSupporterMissionary(supporter_id: str, missionary_id)
  • getMissionaries()
  • getMissionary(missionary_id)

CRM is read-only today (no create* methods), so there are no CRM request objects to document.

CRM Config

>>> kardia.crm_config

Known bugs (see Known Quirks): getTrackCollaborator(s) actually queries the Steps endpoint, and getDocumentTypes() actually queries DataItemTypes instead of DocumentTypes.

Methods

  • getTrack(track_name: str)
  • getTracks()
  • getTrackStep(track_name: str, step_name: str)
  • getTrackSteps(track_name: str)
  • getStepCollaborator(track_name: str, step_name: str, collab_id: str)
  • getStepCollaborators(track_name: str, step_name: str)
  • getTrackCollaborator(track_name: str, step_name: str) — ⚠️ see known bugs above
  • getTrackCollaborators(track_name: str) — ⚠️ see known bugs above
  • getTagType(tag_id: int)
  • getTagTypes()
  • getContactHistType(ch_type_id: int)
  • getContactHistTypes()
  • getCollaboratorType(collab_type_id: int)
  • getCollaboratorTypes()
  • getTodoType(todo_type_id)
  • getTodoTypes()
  • getWorkflowType(wf_type_id)
  • getWorkflowTypes()
  • getDataItemType(di_type_id)
  • getDataItemTypes()
  • getDocumentType(doc_type_id)
  • getDocumentTypes() — ⚠️ see known bugs above
  • getCountry(country: str)
  • getCountries()
  • getTextExpansion(expansion: str)
  • getTextExpansions()
  • createTrack(track: Track)
  • createTrackStep(track_name: str, step: Step)
  • createStepCollaborator(track_name: str, step_name: str, collaborator: StepCollaborator)
  • createSetRequirement(track_name: str, step_name: str, req: StepRequirement)

Designation

>>> kardia.designation

Methods

  • getFundFundingTargets(ledger_id: str, fund_id: str)
  • getFundingTargets()
  • getFundingTarget(ledger_id: str, fund_id: str, target_id: str)
  • getNextFundingTargetID(ledger_id: str, fund_id: str)
  • createFundingTarget(funding_target: FundingTarget)
  • getFundAdminFees(ledger_id: str, fund_id: str)
  • getAdminFees()
  • getAdminFee(ledger_id: str, fund_id: str)
  • createAdminFee(admin_fee: AdminFee)
  • getFundReceiptings(ledger_id: str, fund_id: str)
  • getReceiptings()
  • getReceipting(ledger_id: str, fund_id: str)
  • createReceipting(receipting: Receipting)
  • getFundReceiptingAccounts(ledger_id: str, fund_id: str)
  • getReceiptingAccounts()
  • getReceiptingAccount(ledger_id: str, fund_id: str, account_id: str)
  • createReceiptingAccount(receipting_account: ReceiptingAccount)

Disbursements

>>> kardia.disb

Disbursements subclasses GL, so a kardia.disb instance also has every GL method in addition to the ones below.

Methods

  • getLineItem(ledger: str, year: str, month: str, batch: int, check: int, line_item: int)
  • getLineItems(ledger: str, year: str, month: str, batch: int)
  • createLineItem(year: str, line_item: LineItem)
  • createLineItems(year: str, line_items: List[LineItem])
  • createBatchWithLineItems(ledger: str, year: str, month: str, line_items: List[LineItem], desc: str = "", date: datetime = None, post: bool = False, gl_post: bool = False) — raises if batch or line item creation fails

Donor

>>> kardia.donor

Methods

  • getDonor(partner_id: str)
  • getDonors()
  • getDonorGifts(partner_id: str)
  • getGiftReceipt(partner_id: str, gift_number: int)
  • getYear(partner_id: str, year_id: str)
  • getYears(partner_id: str)
  • getYearGift(partner_id: str, year_id: str, gift_number: int)
  • getYearGifts(partner_id: str, year_id: str)
  • getYearGiftReceipt(partner_id: str, year_id: str, gift_number: int)
  • getYearFund(partner_id: str, year_id: str, fund_id: str)
  • getYearFunds(partner_id: str, year_id: str)
  • getYearFundGift(partner_id: str, year_id: str, fund_id: str, gift_number: int)
  • getYearFundGifts(partner_id: str, year_id: str, fund_id: str)
  • getYearFundGiftReceipt(partner_id: str, year_id: str, fund_id: str, gift_number: int)
  • getFund(partner_id: str, fund_id: str)
  • getFunds(partner_id: str)
  • getFundGift(partner_id: str, fund_id: str, gift_number: int)
  • getFundGifts(partner_id: str, fund_id: str)
  • getFundGiftReceipt(partner_id: str, fund_id: str, gift_number: int)
  • getFundYear(partner_id: str, fund_id: str, year_id: str)
  • getFundYears(partner_id: str, fund_id: str)
  • getFundYearGift(partner_id: str, fund_id: str, year_id: str, gift_number: int)
  • getFundYearGifts(partner_id: str, fund_id: str, year_id: str)
  • getFundYearGiftReceipt(partner_id: str, fund_id: str, year_id: str, gift_number: int)
  • getGivingInfo(partner_id: str, year_id: str)
  • getDonorInfo(partner_id: str, year_id: str)
  • getSettings()
  • getSetting(donor_id: str, ledger_id: str)
  • createSettings(settings: Settings)

getDonorGift(partner_id, gift_number) exists on the class but is unimplemented (# TODO Figure out how to post a gift, always returns None) — use getYearGift/getFundGift instead.

Files

>>> kardia.files

Methods

  • getFile(filepath: str)

Fund Manager

>>> kardia.fundmanager

Methods

  • getSettings(manager_id: str)
  • getSetting(ledger_id: str, fund_id: str, manager_id: str)
  • createSettings(settings: Settings)
  • getFundManagers()
  • getFundManager(manager_id: str)
  • getFunds(manager_id: str)

Gift

>>> kardia.gift

Methods

  • getLedger(ledger_id: str)
  • getLedgers()
  • getBatch(ledger_id: str, batch_id: int)
  • getBatches(ledger_id: str)
  • createBatch(batch: Batch, ledger_id: str)
  • getGift(ledger_id: str, batch_id: int, gift_id: int)
  • getGifts(ledger_id: str, batch_id: int)
  • createGift(gift: Gift, ledger_id: str, batch_id: int)
  • getGiftItem(ledger_id: str, batch_id: str, gift_id: int, item_id: int)
  • getGiftItems(ledger_id: str, batch_id: int, gift_id: int)
  • createGiftItem(item: GiftItem, ledger_id: str, batch_id: int, gift_id: int)
  • getNextReceiptNumber(ledger_id: str)
  • createEGGiftImport(eg_gift_import: EGGiftImport, ledger_id: str)
  • createBatchWithGifts(ledger: str, period: str, gifts: List[Gift], desc: str = None, date: datetime = None, posted: bool = False, posted_to_gl: bool = False)

createBatchWithGifts is the composite "record a batch of gifts" flow — it creates the batch, then each Gift (auto-filling ledger/batch/gift numbers, defaulting the amount from its item_objects, and looking up a receipt number via getNextReceiptNumber if one wasn't set), then each gift's GiftItems. Unlike Disbursements.createBatchWithLineItems/GL.createJEWithTrans, it does not raise if batch creation fails — it silently returns {"Batch": <response>, "Gifts": []}, so check result["Batch"].status_code yourself:

from kardia.objects.gift_objects import Gift, GiftItem
from decimal import Decimal

item = GiftItem(fund="F1", account_code="5000", amount=Decimal("10.00"))
gift = Gift(amount=Decimal("10.00"), partner="P1", item_objects=[item])

result = kardia.gift.createBatchWithGifts("L1", "2024.01", [gift], desc="Sunday offering")
if result["Batch"].status_code != 201:
    raise RuntimeError(f"Batch creation failed: {result['Batch'].content}")
for gift_result in result["Gifts"]:
    print(gift_result["Gift"].status_code, [r.status_code for r in gift_result["Items"]])

GL

>>> kardia.gl

Methods

  • getAccounts(ledger_id: str)
  • getAccount(ledger_id: str, account_id: str)
  • createAccount(account: Account)
  • getAllFunds(ledger_id)
  • getFunds(ledger_id)
  • getFund(ledger_id, fund_id)
  • createFund(fund: Fund)
  • getSubfunds(ledger_id, fund_id)
  • getSubfund(ledger_id, fund_id, subfund_id)
  • createSubFund(fund: Fund)
  • getBatches(ledger_id, year_id, period_id)
  • getBatch(ledger_id, year_id, period_id, batch_id)
  • getNextBatchId(ledger_id)
  • createBatch(year_id, batch: Batch)
  • getPeriods(ledger_id, year_id)
  • getPeriod(ledger_id, year_id, period_id)
  • createPeriod(period: Period)
  • getYears(ledger_id: str)
  • getYear(ledger_id: str, year_id: str)
  • createYear(year: Year)
  • createMonth(ledger_id: str, year_id: str, month: int, month_id: str = None, status: PeriodStatuses = PeriodStatuses.NEVER_OPENED)
  • getTransactions(ledger_id, year_id, period_id, batch_id)
  • createJEWithTrans(ledger_id, year_id, period_id, transactions: List[Transaction], journal_id=1, batch_id=None, date=None, desc="", post=True)
  • createTransaction(year_id: str, trans: Transaction)
  • createTransactions(year_id: str, transactions: List[Transaction])
  • getTransaction(ledger_id: str, year_id: str, period_id: str, batch_id: int, trans_id: int, journal_id: int = 1)
  • getAnalysisAttributes(ledger_id: str)
  • getAnalysisAttribute(ledger_id: str, attr_code: str)
  • createAnalysisAttribute(analysis_attribute: AnalysisAttribute)
  • getAnalysisAttributeValues(ledger_id: str)
  • getAnalysisAttributeValue(ledger_id: str, attr_code: str, value: str)
  • createAnalysisAttributeValue(analysis_attribute_value: AnalysisAttributeValue)
  • getAccountAnalysisAttributes(ledger_id: str)
  • getAccountAnalysisAttribute(ledger_id: str, account_code: str, attr_code: str)
  • createAccountAnalysisAttribute(account_analysis_attribute: AccountAnalysisAttribute)
  • getAccountAccountAnalysisAttributes(ledger_id: str, account_code: str)
  • getFundAnalysisAttributes(ledger_id: str)
  • getFundAnalysisAttribute(ledger_id: str, fund_id: str, attr_code: str)
  • createFundAnalysisAttribute(fund_analysis_attribute: FundAnalysisAttribute)
  • getFundFundAnalysisAttributes(ledger_id: str, fund_id: str)

createJEWithTrans is the composite "create a batch (or use an existing one) plus its transactions" flow: it looks up the next batch ID (unless batch_id is given), creates the batch, then creates each transaction, raising if any step fails. createBatchWithLineItems (see Disbursements) is the disbursement-ledger equivalent and additionally posts balancing GL transactions when gl_post=True.

Partner

>>> kardia.partner

Methods

  • getPartners()
  • getPartner(partner_id: str)
  • createPartner(partner: Partner)
  • getPartnerAddresses(partner_id: str)
  • getPartnerAddress(partner_id, location_id: str)
  • getNextPartnerAddressID(partner_id: str)
  • createPartnerAddress(address: Address)
  • getPartnerContactInfos(partner_id: str)
  • getPartnerContactInfo(partner_id: str, contact_id: str)
  • getNextPartnerContactInfoID(partner_id: str)
  • createPartnerContactInfo(contact_info: ContactInfo)
  • getPartnerSubscriptions(partner_id: str)
  • getPartnerSubscription(partner_id: str, list_id: str)
  • getStaff()
  • createStaff(staff: Staff)
  • getStaffMember(staff_id: str)
  • getStaffLogins()
  • getStaffLogin(username: str)
  • getContactTypes()
  • getContactType(contact_type)
  • getTests()
  • getTest(test)
  • getNextPartnerKey()

createPartner is a composite call: it creates the Partner (auto-generating a key via getNextPartnerKey if partner.p_partner_key is None), then its staff_object, then each of its address_objects/contact_info_objects — cascading the partner key onto every sub-object first. It returns a dict of the individual responses, e.g. {"Partner": <Response>, "Staff": <Response>, "Addresses": [<Response>, ...], "ContactInfos": [<Response>, ...]} (plus "NextPartnerKey" if a key had to be generated).

Report

>>> kardia.report

Methods

  • getReport(report_file: str, report_params: Dict[str, str])
  • getSchedReportsToBeSent()
  • getSchedReportParams(sched_report_name)
  • updateSchedReportStatus(sched_report_name, sched_report_status: SchedReportStatus)
  • getSchedReportBatches()
  • updateSchedReportBatchStatus(sched_report_batch_name, sched_report_batch_status: SchedReportBatchStatus)

getReport runs a .rpt report file directly (e.g. report_file="rcpt/fund_gift_list.rpt") against the bare Kardia URL (not /api), passing report_params as query params.

System Config

modules/sys_config.py defines a SysConfig class (getConfig(ledger)), but it is not currently wired up — the sys_config property on Kardia is commented out in kardia.py, so kardia.sys_config doesn't work. Use kardia.register_module("sys_config", sys_config.SysConfig) (see Custom Modules) if you need it before it's re-enabled.

Custom Modules


Register your own module to reach an endpoint that isn't covered by a built-in module. A custom module is a normal APIModule subclass, written the same way as any built-in module (see e.g. kardia/modules/config.py):

from enum import Enum
from kardia.modules.api_module import APIModule

class Endpoints(Enum):
    PARENT = None
    THING = "Thing"

class MyModule(APIModule):
    def __init__(self, kardia_url, user, pw):
        super().__init__(kardia_url, user, pw)
        self.module = "mymodule"

    def getThing(self, thing_id):
        return self._getEndpoint(Endpoints.THING, thing_id)
>>> kardia.register_module("my_module", MyModule)
>>> kardia.my_module.getThing("T1")

register_module also returns the constructed instance, so you can capture it in a typed local variable for IDE autocomplete/type-checking instead of relying on kardia.<name> (which, being resolved dynamically, isn't visible to static analysis):

>>> my_module: MyModule = kardia.register_module("my_module", MyModule)
>>> my_module.getThing("T1")  # autocompletes, since my_module is now a plain MyModule reference

kardia.<name> and the instance returned by register_module are the same object, constructed once and cached — subsequent access via either form never re-constructs it. register_module raises TypeError if module_class isn't an APIModule subclass, and ValueError if name collides with an existing Kardia attribute.

URI Params


Kardia uses vairous URI parameters to determine what type should be returned and how it is formatted. See Centrallix REST Interface URI Parameters. There are four ways that these paramaters can be set.

Auto

API modules will by default try to determine the correct URI params.

>>> res = kardia.gl.getFunds("LEDGER")
>>> res.url
.../api/gl/LEDGER/Funds/?cx__mode=rest&cx__res_type=collection&cx__res_attrs=basic

>>> res = kardia.gl.getFund("LEDGER", "10001")
>>> res.url
.../api/gl/LEDGER/Funds/10001|LEDGER?cx__mode=rest&cx__res_format=attrs

Type Properties

API modules can set the res_type (and, for element, that type's res_format) with the three properties: collection, element, and both. This allows for chaining methods.

>>> res = kardia.gl.element.getFunds("LEDGER")
>>> res.url
.../api/gl/LEDGER/Funds/?cx__mode=rest&cx__res_type=element&cx__res_format=attrs

>>> res = kardia.gl.collection.getFund("LEDGER", "10001")
>>> res.url
.../api/gl/LEDGER/Funds/10001|LEDGER?cx__mode=rest&cx__res_type=collection&cx__res_attrs=basic

>>> res = kardia.gl.both.getFund("LEDGER", "10001")
>>> res.url
.../api/gl/LEDGER/Funds/10001|LEDGER?cx__mode=rest&cx__res_type=both

Builder Methods

API modules also have chainable methods for setting each URI param individually: res_type(value), res_mode(value), res_format(value), res_attrs(value), res_levels(value). These are useful when the Type Properties above don't cover the specific combination of params you need — e.g. setting res_attrs without setting every other param via setParams().

>>> res = kardia.gl.res_type("collection").res_format("attrs").res_attrs("full").res_levels(2).getFunds("LEDGER")
>>> res.url
.../api/gl/LEDGER/Funds/?cx__mode=rest&cx__res_type=collection&cx__res_format=attrs&cx__res_attrs=full&cx__res_levels=2

Params Method

API modules have a setParams() method which allows for setting any and all of the URI params. This also allows for chaining methods.

>>> res = kardia.gl.setParams(res_type="collection", res_format="attrs", res_attrs="full", res_levels=1).getFunds("LEDGER")
>>> res.url
.../api/gl/LEDGER/Funds/?cx__mode=rest&cx__res_type=collection&cx__res_format=attrs&cx__res_attrs=full

Development and Testing


Install editable with test dependencies, then run the mock suite (fast, no live server needed):

$ python -m pip install -e ".[test]"
$ python -m unittest discover -s tests -p "test_*_mock.py"

Running the full python -m unittest discover also picks up the live integration suite, which needs a real Kardia server and tests/config.toml (copy tests/config.template and fill in credentials/fixture IDs) — see CLAUDE.md for the full testing setup and architecture notes.

Release files for kardia 0.0.39

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

Source distribution (sdist)

Source distribution for kardia 0.0.39
File Size Uploaded
kardia-0.0.39.tar.gz 99.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kardia 0.0.39
File Interpreter ABI Platform
kardia-0.0.39-py3-none-any.whl Python 3 none any Details

Total release size: 155.1 kB

Release files / kardia-0.0.39.tar.gz

Download URL kardia-0.0.39.tar.gz
Size 99.2 kB
Tags Source
SHA-256 checksum
How to use checksums
acc9aecd1e0f821cc917356d94b43af388647908ce8776eea406c12b1890630b
BLAKE2b-256 checksum
How to use checksums
90a02160e5e7d1bbd1a2d92f5c3d623e3c923b19a8a5b125a02b09842dc38540
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / kardia-0.0.39-py3-none-any.whl

Download URL kardia-0.0.39-py3-none-any.whl
Size 55.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
678a2ddcc4376046c2426739bd98096907a97c6e888b5b5f8f5a7580c4668d51
BLAKE2b-256 checksum
How to use checksums
26458e4c36b414541b1c893d3aace692a134867f75e5762c48b015a326b3c301
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

0.0.39 This release

2 release files

0.0.38

2 release files

0.0.37

2 release files

0.0.36

2 release files

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