User Guide

Knackpy #

Build Coverage Python

Knackpy is a Python client for interacting with Knack applications.

Here’s the complete documentation.

Installation #

$ pip install knackpy

Quick Start #

>>> import knackpy

# basic app construction
>>> app = knackpy.App(app_id="myappid",  api_key="myverysecretapikey")

# fetch all records from 'object_1'
>>> records = app.get("object_1")

# get the formatted keys/values of each record
>>> records_formatted = [record.format() for record in records]

# access a record property by name
>>> customer_address = records[0]["Customer Address"]

# create a record
>>> data = {"field_1": "pizza"}
>>> record = app.record(method="create", data=data, obj="object_1")

# download files from an object or view
>>> app.download(
...     container="object_1",
...     field="field_1",
...     out_dir="_downloads"
... )

# upload a file and attach it to a record
>>> app.upload(
...     container="object_1",  # must be an object key or name
...     field="field_3",
...     path="file.jpg",
...     asset_type="file",  # must be 'file' or 'image', depending on field type
...     record_id="5d7968c8092e7f00106c6399"
... )

Working with Apps #

Knackpy is designed around the App class. It provides helpers for querying and manipulating Knack application data. You should use the App class because:

  • It allows you to query objects and views by key or name
  • It takes care of localization issues
  • It lets you download and upload files from your app.
  • It does other things, too.

To create an App instance, the bare minimum you need to provide is your application ID.

If you construct an App instance without providing an API key, you will only be able to fetch records from publicly-availble views.

Note that fetching data from public views is a smart way to avoid hitting your API limit.

# basic app construction with api key
>>> import knackpy
>>> app = knackpy.App(app_id="myappid", api_key="myverysecretapikey")

Getting Records #

Use App.get() to fetch records from a Knack application. Container identifiers can be supplied as a key (object_1, view_1) or a name (my_exciting_object, My Exciting View).

# fetch all records from object_1
>>> records = app.get("object_1")
# or
# fetch all records from view named "My Exciting View"
>>> records = app.get("My Exciting View")

For faster performance, set generate=true when getting records from your app. This will prevent knackpy from constructing all Record instances up-front by returning a generator function instead of a list.

>>> records = app.get("object_1", generate=true)
>>> for record in records:
...     formatted_record = record.format()

Be Careful When Using Named References #

Namespace conflicts are highly likely when fetching by name, because Knack uses object names as the default name for views. If you attempt to query your application by a name that exists as both an object and a view, Knackpy will raise a ValueError.

You can reuse the same App instance to fetch records from other objects and views.

>>> app.get("my_exciting_object")
>>> app.get("my_boring_object")
>>> app.get("view_1")

Omitting the Container Name #

If you’ve only fetched one container, you can omit the container name when accessing your records. This is helpful during development, but for readability we suggest you avoid this practice in production code.

>>> records = app.get("My Exciting View")
# you can omit the container name if you want to access your records again
>>> same_records_without_accessor = app.get()

You can refine your record requests by specififying a record_limit, filters, and timeout. See the module documentaiton for details.

>>> filters = {
    "match": "or",
    "rules": [
        {"field": "field_1", "operator": "is", "value": 1},
        {"field": "field_2", "operator": "is", "value": "Pizza"},
    ],
}
>>> records = app.get("object_1", record_limit=10, filters=filters)

Creating, Updating, and Deleting Records #

Create a record.

>>> app = knackpy.App(app_id="myappid",  api_key="myverysecretapikey")
>>> data = {"field_1": "pizza"}
>>> record = app.record(method="create", data=data, obj="object_1")

Update a record.

>>> app = knackpy.App(app_id="myappid",  api_key="myverysecretapikey")
>>> record = app.get("object_1")[0]
>>> data = dict(record)
>>> data["field_1"] = "new value"
>>> record = app.record(method="update", data=data, obj="object_1")

Delete a record.

>>> response = app.record(method="delete", data={"id": "abc123xyz789"}, obj="object_1")
# response == {"delete": True}

Download Files #

Download files from an object or view.

  • container (str): The name or key of the object or view from which files will be downloaded.

  • field (str): The field key of the file or image field to be downloaded.

  • out_dir (str, optional): Relative path to the directory to which files will be written. Defaults to “_downloads”.

  • label_keys (list, optional): A list of field keys whose values will be prepended to the attachment filename, separated by an underscore.

>>> app.download(
...     container="object_1",
...     field="field_1",
...     out_dir="_downloads",
...     label_keys="field_2"
... )

Upload Files #

Upload a file and attach it to a Knack record.

>>> res = app.upload(
...     container="object_1",  # must be an object key or name
...     field="field_3",
...     path="file.jpg",
...     asset_type="file",  # must be 'file' or 'image', depending on field type
...     record_id="5d7968c8092e7f00106c6399"
... )

Advanced App Usage #

Raw record data is available at App.data. You can use this property to check the readily available data in your App instance.

>>> app.data.keys()
["object_1", "object_2", "view_1"]

References to all available endpoints are stored at App.containers. This is handy if you want to check the name of a container, or its key:

>>> app.containers
[
    Container(obj='object_1', view=None, scene=None, name='my_boring_object'),
    Container(obj='object_2', view=None, scene=None, name='my_exciting_object'),
    Container(obj=None, view='view_1', scene='scene_1', name='My Exciting View'),
]

You can avoid an API call by providing your own Knack metadata when creating an App instance (unclear if metadata requests count against API limits)

>>> import json
# get your app's metadata here: https://loader.knack.com/v1/applications/<app_id:str>"
>>> with open("my_metadata.json", "r") as fin:
...     metadata = json.loads(fin.read())
>> app = knackpy.App(app_id,  metadata=metadata)

You can side-load record data into your your app as well. Note that you must assign your data to a valid key that exists in your app.

>>> with open("my_knack_data.json", "r") as fin:
...     data = { "object_3": json.loads(fin.read()) }
>> app.data = data
>> records = app.get("object_3")

You can use knackpy.get() to fetch “raw” data from your Knack app. Be aware that raw Knack timestamps are problematic. See the Records documentation.

Other App Methods #

Display summary metrics about the app.

>>> app.info()
{'objects': 10, 'scenes': 4, 'records': 6786, 'size': '25.47mb'}

Write a container to CSV. Be aware that destination files will be overwritten, if they exist.

>>> app.to_csv("my exciting view", out_dir="_csv")

Working with Record Objects #

Record objects are dict-like containers for Knack record data. Note that all timestamps have been correctly set to unix time.

You can access a record value like you would a dict, using the field key or field name.

>>> record = app.get("object_1")[0]
# access a value via field key
>>> record["field_22"]
{"city": "Austin", "state": "TX", "street": "8700 Cameron Rd", "street2": "Suite 1", "zip": "78754"}
# access a value via field name
>>> record["Customer Address"]
{"city": "Austin", "state": "TX", "street": "8700 Cameron Rd", "street2": "Suite 1", "zip": "78754"}

Formatting Records #

Format the keys and/or values of a record.

# format keys and values
>>> formatted_record = record.format()
{ "id": "abc123xyz789", "Customer Address": "8700 Cameron Rd, Austin, TX, 78754" }
# only format the keys
>>> formatted_keys = record.format(values=False)
{ "id": "abc123xyz789", "Customer Address": {"city": "Austin", "state": "TX", "street": "8700 Cameron Rd", "street2": "Suite 1", "zip": "78754"}}
# only format the values
>>> formatted_values = record.format(keys=False)
{ "id": "abc123xyz789", "field_22": "8700 Cameron Rd, Austin, TX, 78754" }
# format specific keys and values
>>> formatted_values = record.format(keys=["field_22"], values=["field_22"])
{ "id": "abc123xyz789", "Customer Address": "8700 Cameron Rd, Austin, TX, 78754" }

Although a Record object looks like a dict, it contains Field objects (TODO: link to docs). If you want to convert a Record object into a plain-old dict, use the dict() built-in.

>>> record = app.get("object_1")[0]
>>> data = dict(record)

Dict-like Record methods (Items, Keys, and Names) #

  • Record.items(): returns a list of the knackpy Field objects contained within the Record.
  • Record.keys(): returns a list (not a view) of the record’s field keys.
  • Record.names(): returns a list of the record’s field names.

Accessing Raw Knack Data #

Records may look raw, but any timestamps have been corrected to real unix time. If you want the raw, untouched data, use the Record.raw property.

Accessing the API Directly #

If you don’t want to fuss with App objects, you can use knackpy’s direct interface with the Knack API.

# This is equivalent to exporting records in JSON format from the Knack Builder
>>> import knackpy
>>> data = knackpy.get(
...     app_id="myappid",
...     api_key="myverysecretapikey",
...     obj="object_1",
...     record_limit=None,
...     timeout=30
... )

See the API module documentation for further detail.

Timestamps and Localization #

Although the Knack API returns timestamp values as Unix timestamps in millesconds, these raw values represent millisecond timestamps in your localized timezone. For example a Knack timestamp value of 1578254700000 represents Sunday, January 5, 2020 8:05:00 PM local time.

To address this, the App class converts Knack timestamps into real (UTC-based) unix timestamps. Timestamps are corrected by referencing the timezone info in your apps metadata. You can manually override your app’s timezone information by passing an IANA-compliant timezone string to your App instance, like so:

>>> app = knackpy.App(app_id="myappid",  api_key="myverysecretapikey", tzinfo="US/Eastern")

If you’d like to access your raw, uncorrected records, they can be found at App.data[<container_name:str>].

Note also that Record objects return corrected timestamps via Record.raw and Record.format(). So,

>>> app = knackpy.App(app_id="myappid",  api_key="myverysecretapikey", tzinfo="US/Eastern")
# yields raw records with corrected millisecond timestamps
>>> records_raw = [record.raw for record in app.get("object_3")]
# yields corrected timestamps as ISO-8601 strings
>>> records_formatted = [record.format() for record in app.get("object_3")]

Exceptions #

Knackpy uses Python’s built-in exceptions, as well as Requests’s exceptions when interacting with the Knack API.

If you need to inspect an API exception (for example to see the text content of the response), you can access the Response object by handling the exception like so:

>>> import requests
>>>
>>> app = knackpy.App(api_key="myappid")
# raises HTTPError. You cannot request object records without supplying an API key
>>> try:
...       records = app.get("object_1")
... except requests.exceptions.HTTPError as e:
...     print(e.response.text)
...     raise e
# 'Unauthorized Object Access'

menu: main title: User Guide #