GithubHelp home page GithubHelp logo

cityofaustin / knackpy Goto Github PK

View Code? Open in Web Editor NEW
39.0 14.0 16.0 2.53 MB

A Python client for interacting with Knack applications

Home Page: https://cityofaustin.github.io/knackpy/docs/user-guide/

License: Other

Python 100.00%
python etl data api knack knackhq python-client csv api-wrapper hacktoberfest

knackpy's Introduction

Knackpy

Build Coverage Python

Knackpy is a Python client for interacting with Knack applications. It is written and maintained by municipal tech workers at the City of Austin Transportation Department.

v1.0 is Now Available

Knackpy v1.0 is a complete overhaul of the knackpy API.

Get started with our user guide, API reference, and, for contributors, our developer guide.

The legacy Knackpy v0.1 documentation is available here

knackpy's People

Contributors

chiaberry avatar frankhereford avatar gabcoyne avatar hmnd avatar hotemetoot avatar johnclary avatar spinosa-medly avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

knackpy's Issues

Improve formatting of array values on views

If a column is added to a view from an object that has a many-to-one relationship with the view's source object, the field's values will be contained in an array.

Knackpy formatting will often fail and return the raw data for the field when it encounters an array of values. Here's part of the code to focus on.

We need to rework the field formatting logic to handle these cases where the field holds an array of values.

Update connected field value

Hi all,
First of all, thanks for this wonderful package.
I'm using Knackpy to create records on a knack's db. Everything works (slowly, but it works), except one thing: I'm not able to assign a value to a connected field, remaining always blank.

I have two db, "db.Projects" and "db.Docs" (ID: "object_6"). dbProjects has a field "Project Code" that connects with db.Docs.

Let's say one of the "Project code" values is "ATM001", with internal ID "6221efc2f1a20c001e957bc7" .

On db.Docs, the connected field shows as (obtained from a manually entered record):

"field_36":"<span class=\"6221efc2f1a20c001e957bc7\">ATM001</span>", "field_36_raw":[{"id":"6221efc2f1a20c001e957bc7","identifier":"ATM001"}]

So far I've tried (none of them working)

  1. data["field_36"]="ATM001"
  2. data["field_36"]="6221efc2f1a20c001e957bc7"
  3. data["field_36"]="<span class="6221efc2f1a20c001e957bc7">ATM001"
  4. data["field_36_raw"]=[{"id":"6221efc2f1a20c001e957bc7","identifier":"ATM001"}]
    (and then creating a new record with:
    app.record(method="create", data=data,obj="object_6")

Any idea of which is the correct way of doing this, or any turnaround?

Thanks!

=== UPDATE ===
I'm now able to update the connected field value on a second step. So first I create the record (method="create") and then I update the values (method="update"). It will take twice the time, but... it works.

small typo (I think)

Hi. I found what seems to be a typo in the instructions for retrieving data directly from an object. There is no comma aftert the line "obj ='object_1'"

>>> kn = Knack(
      obj='object_1'
      app_id='abc123',
      api_key='topsecretapikey'
    )

Anyhow, thanks so mych for writing this! It is really fantastic for backing up data and also for integrating Knack with Power BI.

Filters require refresh=True

Part of #89

In order to query with a filter, the

app.get(object_view, filters=filter) 

requires explicitly passing refresh=True

What would help is if this line

if self.records.get(container_key) and not refresh:

and
if not self.data.get(container_key) or refresh:

also checked to see if filters was not None, or even easier would be to explicitly set refresh to True if filters are passed in

def get(
        self,
        identifier: str = None,
        refresh: bool = False,
        record_limit: int = None,
        filters: typing.Union[dict, list] = None,
        generate=False,
    ):
.....

if not refresh : 
    if not filters == None : 
        refresh = True

Happy to create a PR if you want one

Unexpected behavior when using `Record.format`

You can specify the keys and values you want to format with the Record.format method (docs).

formatted = record.format(keys=True, values=["field_1", "field_2"])

This behavior is not consistent when formatting values, due to some unfortunate variable shadowing of key, right here.

Support for fetching from the record history API

You can query a record's edit history here using your standard headers: https://us-east-1-builder-read.knack.com/v1/objects/<object name>/records/<record id>/history

Perhaps implement this as a Record method, e.g. Record.get_history(), or App.get_history(object_id, record_id)

Help with dates

Hi,
Is there anyway of returning a specific format of date ?
My object has calculated dates and user entry and both fields return different formats eg.

  • Calculated field returns UNIX/Epoch value : 1576735200000

  • User entry field returns all fomatting options : {'date': '12/19/2019', 'date_formatted': '19/12/2019', 'hours': '12', 'minutes': '00', 'am_pm': 'AM', 'unix_timestamp': 1576713600000, 'iso_timestamp': '2019-12-19T00:00:00.000Z', 'timestamp': '12/19/2019 12:00 am', 'time': 0}

Ideally I would like both to return a specified format (preferably UK formatted ddmmyyyy or ISO yyyymmdd)

Thank you for creating this
(pls note I am a brand new user to python so apologies if this request makes no sense)

get_app_data does not correctly handle 502 error

Knack is down at the moment and sending this request: "https://loader.knack.com/v1/applications/my app id" Gives a 502 error.

Calling knackpy.get_app_data() throws an exception:

knack_app_info = get_app_data(knack_app_id)
  File "...../python3.6/site-packages/knackpy/__init__.py", line 516, in get_app_data
    raise Exception(req.text)
NameError: name 'req' is not defined

Need to fix the error handling to cover this case.

Source field names from views

In the Knack builder, field labels can be customized for any view, however these field labels are not currently exposed in the Record class. Propose that we use these labels, possibly via optional flag (for backwards compatibility) when constructing the app or formatting records.

api._continue has a infinite loop bug

I added some debugging into the loop print(f"Getting {len(records)} / {total_records} records from page {page} from {url}"):

Getting 0 / None records from page 1 from https://api.knack.com/v1/objects/object_7/records/
Getting 954 / 6693 records from page 2 from https://api.knack.com/v1/objects/object_7/records/
Getting 1954 / 6693 records from page 3 from https://api.knack.com/v1/objects/object_7/records/
Getting 2954 / 6693 records from page 4 from https://api.knack.com/v1/objects/object_7/records/
Getting 3954 / 6693 records from page 5 from https://api.knack.com/v1/objects/object_7/records/
Getting 4954 / 6693 records from page 6 from https://api.knack.com/v1/objects/object_7/records/
Getting 5954 / 6693 records from page 7 from https://api.knack.com/v1/objects/object_7/records/
Getting 6647 / 6693 records from page 8 from https://api.knack.com/v1/objects/object_7/records/
Getting 6647 / 6693 records from page 9 from https://api.knack.com/v1/objects/object_7/records/
Getting 6647 / 6693 records from page 10 from https://api.knack.com/v1/objects/object_7/records/
Getting 6647 / 6693 records from page 11 from https://api.knack.com/v1/objects/object_7/records/
Getting 6647 / 6693 records from page 12 from https://api.knack.com/v1/objects/object_7/records/
Getting 6647 / 6693 records from page 13 from https://api.knack.com/v1/objects/object_7/records/
Getting 6647 / 6693 records from page 14 from https://api.knack.com/v1/objects/object_7/records/

It looks like even though we requested 1000 rows we only got 954. Should we add a failsafe like below:

while knackpy.api._continue(total_records, len(records), record_limit):
            ...
            fetched_records = res.json()["records"]
            records += fetched_records
            page += 1
            if len(fetched_records) == 0:  # If we're no longer fetching rows, break out of the loop
                break

Handle 5xx errors like timeouts

The Knackpy APIs support a max_attempts value which will retry on request timeouts up to the number of max attempts. Unfortunately, 500 errors are not handled this way, and the Knack API has a terrible habit of returning 503 errors in particular. We should retry on requests that fail with any 5xx error.

Knackpy.record is not working?

`` record1 = {'id':'blabla,'field_496':'1'}

response = Knack.record(
    record1,
    obj_key='object_23',    
    app_id='blabla',
    api_key='blabla',    
    method='update'
    )

I get the response:

AttributeError Traceback (most recent call last)
in ()
4 record1 = {'id':str(i),'field_496':'1'}
5
----> 6 response = Knack.record(
7 record1,
8 obj_key='object_23', #check which object in knack is the table you want to call here

AttributeError: type object 'Knack' has no attribute 'record'

Address field subfield "street_2" missing

When I interact with a field that contains an address it does not return the Street_2 value that is available in the api. When looking at section of the code that deals with subfields it only lists :
if field_type == 'address':
for subfield in ('latitude', 'longitude', 'formatted_value',
'street', 'city', 'state', 'country', 'zip'):

Please can the value for street_2 be added ?

Knack formatting for equation type fields is not preserved

For equation fields, knackpy saves the same (raw) value as both the raw and formatted version of the value. However, the knack formatted version is preferred sometimes, such as when it is a datetime equation. The solution is likely to be adding a function for equation fields to the formatter.

So, here are a couple of snippets from the debugger when using knackpy:

records[0].fields['field_377'].formatted
372171.3180000782
records[0].fields['field_377'].raw
372171.3180000782

records_formatted[0]['Days Waiting - Security Review']
372171.3180000782

And here's a snippet from what we get from our knack script (run in debugger):

self.data_raw[0]['field_377']
4
self.data_raw[0]['field_377_raw']
372171.3180000782

Note that 372171.3180000782 / (60*60*24) = ~4.3 - in other words, the "raw" version returns the value in seconds, while the knack formatted version returns the value in days.

Here's another example (different record) - a snippet from the JSON export of the object:

"field_377":5,"field_377_raw":434357.5299999714,

Knack() Parsing Fails With URL Image Fields

Image fields come in two flavors: direct upload or reference URL. When images are uploaded directly to a field, they are comprised of a dict with "url" key of the image's endpoint. When the image field is configured with a reference URL, the field value is simply a string.

Knack._parse_data() currently fails with an AttributeError if the image field is reference URL.

Unable to run app.get() for some objects in a knack form due to datetime issue

When running app.get("object_1") on a knack form, I get an error that prevents me from getting any app data. Full traceback is copied below. The knack application id is 5cc35f4acaf774317c5c74b4 (see the metadata here) . I'm currently on version 1.1.1 of knackpy. This error only occurs for this one knack form thus far (all others tested have been successful and have not encountered this error), but does occur for some objects (object_1, object_19) in the form, but does not for others (object_4).

app.get("object_1")
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "c:\Users\167753\Documents\GitHub\civis_pipelines\knack-env\lib\site-packages\knackpy\app.py", line 246, in get
    self.records[container_key] = self._records(container_key, generate)
  File "c:\Users\167753\Documents\GitHub\civis_pipelines\knack-env\lib\site-packages\knackpy\app.py", line 281, in _records
    for record in data
  File "c:\Users\167753\Documents\GitHub\civis_pipelines\knack-env\lib\site-packages\knackpy\app.py", line 281, in <listcomp>
    for record in data
  File "c:\Users\167753\Documents\GitHub\civis_pipelines\knack-env\lib\site-packages\knackpy\record.py", line 35, in __init__
    self.raw = self._handle_record()
  File "c:\Users\167753\Documents\GitHub\civis_pipelines\knack-env\lib\site-packages\knackpy\record.py", line 152, in _handle_record
    record = self._correct_knack_timestamp(record, self.timezone)
  File "c:\Users\167753\Documents\GitHub\civis_pipelines\knack-env\lib\site-packages\knackpy\record.py", line 197, in _correct_knack_timestamp
    val["unix_timestamp"], timezone
  File "c:\Users\167753\Documents\GitHub\civis_pipelines\knack-env\lib\site-packages\knackpy\utils.py", line 64, in correct_knack_timestamp
    dt_utc = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
OSError: [Errno 22] Invalid argument

I suspect that this error is caused by some malformed/mistyped dates. I've included one such date from the downloaded JSON file below as an example:

"field_25":"07/03/0198","field_25_raw":{"date":"07/03/0198","date_formatted":"07/03/0198","hours":"12","minutes":"00","am_pm":"AM","unix_timestamp":-55903046400000,"iso_timestamp":"0198-07-03T00:00:00.000Z","timestamp":"07/03/0198 12:00 am","time":720}

Obviously we want to correct these dates, however, this error is preventing me from fetching any data from the app at all. So, it would be best if this error could be handled in such a way that app data can still be accessed/processed.

Sanitize file paths when downloading files

If you pass in a label_key field to the downloads function, and the data in that field has characters that are not allowed in a file name, it does not escape them or gracefully fail. It hard fails with an "invalid argument"

OSError Traceback (most recent call last)
in
10 field="field_508",
11 out_dir="_downloads",
---> 12 label_keys=["field_499"]
13 )

~.conda\envs\ds775\lib\site-packages\knackpy\app.py in download(self, container, field, out_dir, label_keys)
501 )
502
--> 503 download_count = self._download_files(downloads)
504
505 logger.debug(f"{download_count} files downloaded.")

~.conda\envs\ds775\lib\site-packages\knackpy\app.py in _download_files(self, downloads)
458 res.raise_for_status()
459
--> 460 with open(filename, "wb") as fout:
461 fout.write(res.content)
462 count += 1

OSError: [Errno 22] Invalid argument: '_downloads\Youth Define "Constructive" Activity_definitionofconstructive2ndgroup.jpg'

Downloads Function Documentation

Your documentation for your downloads function that the mentions that the label_keys should be a list, but the example shows a string. If you pass in a string, it attempts to parse each letter of the string (treats it like a list).

OSError: [Errno 22] Invalid argument

Hi,
I get the following errors under specific circumstances

Traceback (most recent call last):
File "Z:\System Administrator\System Backups\xxxxxx\Scripts\KNACK OpsManager - Test.py", line 26, in
rows_per_page=1000
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python37-32\lib\site-packages\knackpy\knackpy.py", line 155, in init
self.data = self._parse_data()
File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python37-32\lib\site-packages\knackpy\knackpy.py", line 408, in _parse_data
dt_utc = datetime.utcfromtimestamp(d / 1000)
OSError: [Errno 22] Invalid argument

I have tried running various stripped down scripts and found that this works

from knackpy import Knack
kn = Knack( 
        obj='object_1',     
        app_id='XXXXXX',
        api_key='XXXXXX',
        tzinfo='Europe/London',
        filters = {
              'match': 'and',
              'rules': [
                {
                  'field':'field_31',
                  'operator':'is after',
                  'value':'31/03/2018'
                },
                {
                  'field':'field_31',
                  'operator':'is before',
                  'value':'01/04/2019'
                }
              ]
            },
        page_limit=30,
        rows_per_page=1000
    )

However the same script without a filter fails

from knackpy import Knack
kn = Knack( 
        obj='object_1',        
        app_id=XXXXXX',
        api_key='XXXXXX',
        tzinfo='Europe/London',
        page_limit=30,
        rows_per_page=1000
    )

And if I try a date in the filter that "is before" 07/06/2019 or any date after then it won't work and gives me the error above. Any combination prior to that works fine it doesn't seem to like late 2019 and any of 2020

from knackpy import Knack
kn = Knack( 
        obj='object_1',
        
        app_id='584202acb1d322842fe19657',
        api_key='82fee2f0-b8e6-11e6-9079-a7bdabe089f6',
        tzinfo='Europe/London',
        filters = {
              'match': 'and',
              'rules': [
                {
                  'field':'field_31',
                  'operator':'is after',
                  'value':'31/03/2019'
                },
                {
                  'field':'field_31',
                  'operator':'is before',
                  'value':'08/06/2019'
                }
              ]
            },
        page_limit=30,
        rows_per_page=1000
    )

Many Thanks

Support Writing Unicode to CSV

If your data has unicode characters, you'll need to encode it before you send it to knack.to_csv(). We should be able to handle unicode by default.

Perhaps by encoding the data if we catch a UnicdeError. E.g. with [{key : d[key].encode()} for d in data for key in d ]

Size constraints on knackpy.get() requests?

Howdy

I'm seeing some issues with both the old and new version of the knackpy .get functionality erroring out. Both of these fetches will sometimes work randomly (though 99% of the time they don't).

We can't tell if it's an issue with pagination, improper responses from Knack, or something else.

I've confirmed using Knack's native API that the knackpy GETs are attempting a fetch of 143 pages and 3561 total records for this fetch, with an estimated (total) size of 21MB. For an API fetch this doesn't seem substantially large enough to warrant the errors we're seeing.

For repro, I was having trouble tracking down an exact setup that would trigger this every time. It wasn't a specific subset of records, as it would happen intermittently. It seemed like the number of record's returned was most likely the culprit, but again, we've had success with larger than 4k records plenty of times before. This feels like it's somewhat new, and I don't think response errors should be bubbling up to the native python libraries.

Sorry if this is actually an issue upstream of knackpy.

System

  • Python 3.9
  • knackpy 0.1.1 and 1.0.20

Issue/Repro for knackpy==1.0.20

The following code

# !pip install knackpy==1.0.20
import knackpy

filters = {
    'match': 'and',
    'rules': [
        {
            'field':'field_75',
            'operator':'is after',
            'value':'10/14/2021'
        },
        {
            'field':'field_75',
            'operator':'is before',
            'value':'12/14/2021'
        }
    ]
}

app = knackpy.App(app_id='' ,api_key='')
kn = app.get('object_7',filters=filters)
kn

produced the following error

---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
/var/folders/pr/wdvw7x0s31d94g3qhzd732jh0000gn/T/ipykernel_56667/467188259.py in <module>
     19 
     20 app = knackpy.App(app_id='' ,api_key='')
---> 21 kn = app.get('object_7',filters=filters)
     22 
     23 # kn = Knack(

/usr/local/lib/python3.9/site-packages/knackpy/app.py in get(self, identifier, refresh, record_limit, filters, generate)
    231 
    232         if not self.data.get(container_key) or refresh:
--> 233             self.data[container_key] = api.get(
    234                 app_id=self.app_id,
    235                 api_key=self.api_key,

/usr/local/lib/python3.9/site-packages/knackpy/api.py in get(app_id, api_key, slug, obj, scene, view, record_limit, filters, max_attempts, timeout)
    233         MAX_ROWS_PER_PAGE if record_limit >= MAX_ROWS_PER_PAGE else record_limit
    234     )
--> 235     return _get_paginated_records(
    236         app_id=app_id,
    237         api_key=api_key,

/usr/local/lib/python3.9/site-packages/knackpy/api.py in _get_paginated_records(app_id, url, max_attempts, record_limit, rows_per_page, api_key, timeout, filters)
    178         )
    179 
--> 180         fetched_records = res.json()["records"]
    181         if len(fetched_records) == 0:
    182             """Failsafe to handle edge case in which Knack returns fewer records than expected from 

/usr/local/lib/python3.9/site-packages/requests/models.py in json(self, **kwargs)
    908                     # used.
    909                     pass
--> 910         return complexjson.loads(self.text, **kwargs)
    911 
    912     @property

/usr/local/Cellar/[email protected]/3.9.8/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    344             parse_int is None and parse_float is None and
    345             parse_constant is None and object_pairs_hook is None and not kw):
--> 346         return _default_decoder.decode(s)
    347     if cls is None:
    348         cls = JSONDecoder

/usr/local/Cellar/[email protected]/3.9.8/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/decoder.py in decode(self, s, _w)
    335 
    336         """
--> 337         obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338         end = _w(s, end).end()
    339         if end != len(s):

/usr/local/Cellar/[email protected]/3.9.8/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/decoder.py in raw_decode(self, s, idx)
    351         """
    352         try:
--> 353             obj, end = self.scan_once(s, idx)
    354         except StopIteration as err:
    355             raise JSONDecodeError("Expecting value", s, err.value) from None

JSONDecodeError: Unterminated string starting at: line 1 column 625726 (char 625725)

With the same code I've also gotten different errors akin to

  File "/usr/local/lib/python3.9/site-packages/knackpy/api.py", line 235, in get
    return _get_paginated_records(
  File "/usr/local/lib/python3.9/site-packages/knackpy/api.py", line 180, in _get_paginated_records
    fetched_records = res.json()["records"]        
  File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 910, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/local/Cellar/[email protected]/3.9.8/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "/usr/local/Cellar/[email protected]/3.9.8/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/Cellar/[email protected]/3.9.8/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/decoder.py", line 353, in raw_decode
    obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Expecting ':' delimiter: line 1 column 858400 (char 858399)

Issue/Repro for knackpy==0.1.1

Code:

# !pip install knackpy==0.1.1
from knackpy import Knack

filters = {
    'match': 'and',
    'rules': [
        {
            'field':'field_75',
            'operator':'is after',
            'value':'10/14/2021'
        },
        {
            'field':'field_75',
            'operator':'is before',
            'value':'12/14/2021'
        }
    ]
}

kn = Knack(
    obj='object_7',
    app_id='',
    api_key='',
    timeout = 360,
    filters=filters
)
kn 

Error:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/usr/local/lib/python3.9/site-packages/urllib3/response.py in _update_chunk_length(self)
    696         try:
--> 697             self.chunk_left = int(line, 16)
    698         except ValueError:

ValueError: invalid literal for int() with base 16: b''

During handling of the above exception, another exception occurred:

InvalidChunkLength                        Traceback (most recent call last)
/usr/local/lib/python3.9/site-packages/urllib3/response.py in _error_catcher(self)
    437             try:
--> 438                 yield
    439 

/usr/local/lib/python3.9/site-packages/urllib3/response.py in read_chunked(self, amt, decode_content)
    763             while True:
--> 764                 self._update_chunk_length()
    765                 if self.chunk_left == 0:

/usr/local/lib/python3.9/site-packages/urllib3/response.py in _update_chunk_length(self)
    700             self.close()
--> 701             raise InvalidChunkLength(self, line)
    702 

InvalidChunkLength: InvalidChunkLength(got length b'', 0 bytes read)

During handling of the above exception, another exception occurred:

ProtocolError                             Traceback (most recent call last)
/usr/local/lib/python3.9/site-packages/requests/models.py in generate()
    757                 try:
--> 758                     for chunk in self.raw.stream(chunk_size, decode_content=True):
    759                         yield chunk

/usr/local/lib/python3.9/site-packages/urllib3/response.py in stream(self, amt, decode_content)
    571         if self.chunked and self.supports_chunked_reads():
--> 572             for line in self.read_chunked(amt, decode_content=decode_content):
    573                 yield line

/usr/local/lib/python3.9/site-packages/urllib3/response.py in read_chunked(self, amt, decode_content)
    792             if self._original_response:
--> 793                 self._original_response.close()
    794 

/usr/local/Cellar/[email protected]/3.9.8/Frameworks/Python.framework/Versions/3.9/lib/python3.9/contextlib.py in __exit__(self, typ, value, traceback)
    136             try:
--> 137                 self.gen.throw(typ, value, traceback)
    138             except StopIteration as exc:

/usr/local/lib/python3.9/site-packages/urllib3/response.py in _error_catcher(self)
    454                 # This includes IncompleteRead.
--> 455                 raise ProtocolError("Connection broken: %r" % e, e)
    456 

ProtocolError: ("Connection broken: InvalidChunkLength(got length b'', 0 bytes read)", InvalidChunkLength(got length b'', 0 bytes read))

During handling of the above exception, another exception occurred:

ChunkedEncodingError                      Traceback (most recent call last)
/var/folders/pr/wdvw7x0s31d94g3qhzd732jh0000gn/T/ipykernel_57438/297416796.py in <module>
     18 }
     19 
---> 20 kn = Knack(
     21     obj='object_7',
     22     app_id='',

/usr/local/lib/python3.9/site-packages/knackpy/knackpy.py in __init__(self, api_key, app_id, filters, include_ids, id_key, max_attempts, obj, page_limit, raw_connections, rows_per_page, ref_obj, scene, timeout, tzinfo, view)
    133 
    134         self.endpoint = self._get_endpoint()
--> 135         self.data_raw = self._get_data(self.endpoint, "records", self.filters)
    136 
    137         if not self.data_raw:

/usr/local/lib/python3.9/site-packages/knackpy/knackpy.py in _get_data(self, endpoint, record_type, filters)
    205 
    206                 try:
--> 207                     req = requests.get(
    208                         endpoint, headers=headers, params=params, timeout=self.timeout
    209                     )

/usr/local/lib/python3.9/site-packages/requests/api.py in get(url, params, **kwargs)
     73     """
     74 
---> 75     return request('get', url, params=params, **kwargs)
     76 
     77 

/usr/local/lib/python3.9/site-packages/requests/api.py in request(method, url, **kwargs)
     59     # cases, and look like a memory leak in others.
     60     with sessions.Session() as session:
---> 61         return session.request(method=method, url=url, **kwargs)
     62 
     63 

/usr/local/lib/python3.9/site-packages/requests/sessions.py in request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    540         }
    541         send_kwargs.update(settings)
--> 542         resp = self.send(prep, **send_kwargs)
    543 
    544         return resp

/usr/local/lib/python3.9/site-packages/requests/sessions.py in send(self, request, **kwargs)
    695 
    696         if not stream:
--> 697             r.content
    698 
    699         return r

/usr/local/lib/python3.9/site-packages/requests/models.py in content(self)
    834                 self._content = None
    835             else:
--> 836                 self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b''
    837 
    838         self._content_consumed = True

/usr/local/lib/python3.9/site-packages/requests/models.py in generate()
    759                         yield chunk
    760                 except ProtocolError as e:
--> 761                     raise ChunkedEncodingError(e)
    762                 except DecodeError as e:
    763                     raise ContentDecodingError(e)

ChunkedEncodingError: ("Connection broken: InvalidChunkLength(got length b'', 0 bytes read)", InvalidChunkLength(got length b'', 0 bytes read))

TypeError: string indices must be integers

Hi! Since last Friday I'm having an error when running
records=app.get('object_8')

throwing this:
`File "/usr/local/lib/python3.7/dist-packages/knackpy/formatters.py", line 118, in
identifiers = [conn["identifier"] for conn in value]
TypeError: string indices must be integers'

I'm able to get other objects (much smallers, the one that is giving problem has 8.5k records x 25 columns), so the problem should be on that db. Any suggestions of where or what should I look for?

Thanks

Feature Request: add a formatter for user_roles field type

The user_roles field type is used in User Roles (a.k.a Accounts) type objects. Currently, the only value grabbed (formatted or unformatted) is the role id (e.g. 'profile_34'). However, the more useful information is the human-readable label (e.g. 'Admin'). These human readable labels are available either in the not raw version of the field (but trapped in html), or in the profile_keys field.

Example of dict stored in the records object is copied below:

'profile_keys':'Admin, Applicants, Licensing Board-p',
'profile_keys_raw':[{'id': 'profile_51', 'identifier': 'Admin'}, {'id': 'profile_34', 'identifier': 'Applicants'}, {'id': 'profile_35', 'identifier': 'Licensing Board-p'}],
'field_96':'<span class="profile_51">Admin</span><br /><span class="profile_34">Applicants</span><br /><span class="profile_35">Licensing Board-p</span>',
'field_96_raw':['profile_51', 'profile_34', 'profile_35']

It would be great to have a formatter to handle this specific field type.

MaxRetries Error while fetching 3.6 lakh records from Knack

Hi,
I am trying to fetch around 3.6 lakh records from knack using knackpy.

I have used the to_csv function as below:-

app = knackpy.App(app_id=my_app, api_key=my_key) app.to_csv('object_19', out_dir = csv_dir)

app_id, my_key, csv_dir are referenced from the config file. I am getting the below errors:-

raise MaxRetryError(_pool, url, error or ResponseError(cause)) urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='thinkheyday-api.knack.com', port=443): Max retries exceeded with url: /v1/objects/object_19/records/?page=5&rows_per_page=1000 (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x000001525DC7E550>: Failed to establish a new connection: [Errno 11002] getaddrinfo failed'))

At first, I encountered this issue on page 26 out of 36, then it started coming on page 5.

Can anyone help me here?
I have gone through the Knack docs that Knack has a limited the record extraction as a part of the API limit.

Issue with get function when Knack fields updated as connection fields

See code snipped of function below -

I am trying to perform the get function on object_1 in my knackpy app - it was working successfully and I was able to retrieve the records and transform into a dictionary. Since then, an update to my knackpy app has been made where some fields are now connections - the error message I am getting is - TypeError: string indices must be integers

# take note of the record id associated with each job id (field 6 in knack) in a json format
def add_id_to_log(new_data):
    app = knackpy.App(app_id=settings.APP_ID, api_key=settings.API_KEY, tzinfo="GB")
    get_record = app.get("object_1")
    for i in range(len(get_record)):
        data = dict(get_record[i])
        if data['field_6'] == new_data['field_6']:
            new_id = {data['id']: data['field_6']}
            with open("log.json", "r+") as jsonFile:
                data = json.load(jsonFile)
                data.update(new_id)
                jsonFile.seek(0) 
                json.dump(data, jsonFile)
                jsonFile.truncate()
                jsonFile.close()
        else:
            pass

Make App._download_files() a static method

App._download_files() is in fact a static method. We should decorate it as such and drop self in case someone wants to use it as such. Say, for example, you're doing some crazy file migration and you want to monkey patch knackpy for assistance. Me IRL.

I guess it might be more elegant to move the method into Knackpy.api, since that's where the rest of our low-level API methods exist.

The current workaround is to just construct an app instance and call the method manually, which isn't hard.

def _download_files(self, downloads: list):

Unable to make filters work

Hi Team, thanks for your incredible work. I'm not sure if this is the place to ask for help, so please let me know if I should ask elsewhere.

I'm trying to update Knack's database with a csv file using your package. First I download Knack's db, compare it with the csv, and check for the differences. So far so good.

The problem is updating the records. I want to use one field, "Unique ID", to get the record to update, so I built this:

app=knackpy.App(app_id=appid, api_key=apikey)
for ind in list_of_ID_to_update:
     filtro = [{"field": "Unique ID", "operator": "is","value": id_to_update}]
     records=app.get("object_8", record_limit=1, filters=filtro)
     r2= [record.format() for record in records]

But it always returns the record with 'Unique ID'==1, no matter the value of id_to_update.

I've even tried using a filter that shouldn't return anything:

app=knackpy.App(app_id=appid, api_key=apikey)
filtro = {
        "match":"and",
        "rules":[
            {"field": "Unique ID", "operator": "is","value": 0},
            {"field": "CSL", "operator": "is","value": 'John Doe'}
            ]}
records=app.get("object_8", record_limit=1, filters=filtro)
r2= [record.format() for record in records]

and I always get the first record (or the first 10 records if I change the record_limit).

Any idea of what am I doing wrong?
Thanks

SSLError when using URL Slug for dedicated server

Hi team, thank you for this software.

We've recently moved our Knack apps to a dedicated server at apps.mycompany.com. The endpoint for the api should be api.apps.mycompany.com, but when I try to connect it throws an error:

requests.exceptions.SSLError: HTTPSConnectionPool(host='api.apps.mycompany.com-api.knack.com', port=443): Max retries exceeded with url: /v1/applications/60df87fxxxxxxxxxxxxxxxx (Caused by SSLError(CertificateError("hostname 'api.apps.mycompany.com-api.knack.com' doesn't match '*.knack.com'")))

I've contacted Knack's support, but any help would be much appreciated.

Thanks and best regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.