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 kardiakardia.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:
    • bool1/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:

class Root(Enum):
    PARENT = None
    DONOR_ID = "/"

class Endpoints(Enum):
    PARENT = Root.DONOR_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.GIFTSEndpoints.YEARSRoot.DONOR_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

(the double slash after donor is intentional — see Known Quirks). 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:

  • Triple-slash URLs. donor.py, gl.py/disbursements.py, sys_config.py, acctconfig.py, and fundmanager.py each define a root Enum whose value is the literal string "/", used to slot a ledger_id/manager_id in as the first path segment — this produces a literal triple slash in the real request URL (e.g. kardia.gl.getAccounts("L1")/gl///L1/Accounts/).
  • CRM's double slash. crm.py is unrelated and even more unusual: it never sets self.module and doesn't use the Enum/_getEndpoint pattern at all — it hand-builds self.endpoint through a chain of private helpers, producing its own double-slash pattern.
  • 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.datares.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.

Download files

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

Source Distribution

kardia-0.0.35.tar.gz (98.5 kB view details)

Uploaded Source

Built Distribution

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

kardia-0.0.35-py3-none-any.whl (54.4 kB view details)

Uploaded Python 3

File details

Details for the file kardia-0.0.35.tar.gz.

File metadata

  • Download URL: kardia-0.0.35.tar.gz
  • Upload date:
  • Size: 98.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for kardia-0.0.35.tar.gz
Algorithm Hash digest
SHA256 1c2f5ac355ffd5f533926c78ad77d7850da37291e3ba9120f13160e24e696aa1
MD5 73bd706a34b84310d03e421c49f9f83d
BLAKE2b-256 a0c45d448ac6fda5f0dde628439d63416adb3c9ed20038e87dbb36ba257c31bb

See more details on using hashes here.

File details

Details for the file kardia-0.0.35-py3-none-any.whl.

File metadata

  • Download URL: kardia-0.0.35-py3-none-any.whl
  • Upload date:
  • Size: 54.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for kardia-0.0.35-py3-none-any.whl
Algorithm Hash digest
SHA256 8b4d505424c055f95f44dc9e0605f18ea9115185fdd020759911cc446b3a25e7
MD5 646b8b6b057e7b952515068fae36f01e
BLAKE2b-256 bc76e2e04f1f49d9cdb9c480ef42eed694dcd626a839954e9ba2a139d891faeb

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.35 This release

2 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