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
- Kardia Python API
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 tokardia(pip install kardia,from kardia import Kardia). Existing code that doesimport kardia_apikeeps working unchanged — the old name is now a thin alias package pointing at the same classes — but new code should usekardia.
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_byto the literal string"api"(not your connectinguser— there is no "current user" concept baked into objects) ands_date_created/s_date_modifiedto the current timestamp, on every object you construct. - Provides
serialize(), called internally by everycreate*/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/0Enum→.valuedatetime→{"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 ofserialize()): if an object subclass setsself.required_attrs = ["a_ledger_number", ...]and any of those are falsy when you callcreate*, you get a plainExceptionbefore 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.GIFTS → Endpoints.YEARS → Root.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, andfundmanager.pyeach define a rootEnumwhose value is the literal string"/", used to slot aledger_id/manager_idin 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.pyis unrelated and even more unusual: it never setsself.moduleand doesn't use theEnum/_getEndpointpattern at all — it hand-buildsself.endpointthrough a chain of private helpers, producing its own double-slash pattern. s_created_byis 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)andgetTrackCollaborators(track_name)actually query the Steps endpoint, not a Collaborators endpoint — likely a copy/paste mistake.getDocumentTypes()actually queriesDataItemTypes, notDocumentTypes(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 returnsNoneinstead of raising on an empty or non-JSON body.res.ok_or_raise(message=None)— raisesKardiaAPIError(carrying.response/.status_code) if the response isn't a 2xx, otherwise returnsresso it's chainable:
res = kardia.gl.createFund(fund).ok_or_raise("Failed to create fund")
res.get(name, default=None)— shortcut forres.osml.get(name, default)(defaultif the body isn't a JSON object).res.osml— aKardiaOSMLparse of the response body (Noneif 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 toDecimal/datetimeeither way.KardiaOSMLexposes:.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 childKardiaOSMLifnamenames 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
KardiaResponsedirectly (for member in res) is a shortcut for iteratingres.osml— e.g.for year in kardia.gl.getYears("DM"): print(year). RaisesTypeErrorif the body isn't a JSON object (e.g. a binary file download viakardia.files.getFile) — use.content/.rawfor 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, andgetDocumentTypes()actually queriesDataItemTypesinstead ofDocumentTypes.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c2f5ac355ffd5f533926c78ad77d7850da37291e3ba9120f13160e24e696aa1
|
|
| MD5 |
73bd706a34b84310d03e421c49f9f83d
|
|
| BLAKE2b-256 |
a0c45d448ac6fda5f0dde628439d63416adb3c9ed20038e87dbb36ba257c31bb
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b4d505424c055f95f44dc9e0605f18ea9115185fdd020759911cc446b3a25e7
|
|
| MD5 |
646b8b6b057e7b952515068fae36f01e
|
|
| BLAKE2b-256 |
bc76e2e04f1f49d9cdb9c480ef42eed694dcd626a839954e9ba2a139d891faeb
|