Lever-age / leverage

Empower citizens of Philadelphia to use campaign finance data when making informed decisions about who they donate to, who they support, and who they vote for.
http://leveragecampaignfinance.org/
20 stars 10 forks source link

Candidacy object returned from race_id filter on /candidates endpoint does not include "party" field #60

Open lottspot opened 6 years ago

lottspot commented 6 years ago

Example request:

$ curl -s localhost:5100/api/candidates?race_id=28 | python -m json.tool
{
    "data": [
        {
            "candidacies": [
                {
                    "candidacy_order": 3,
                    "candidacy_type": "challenger",
                    "candidate_id": 94,
                    "id": 3,
                    "outcome": "upcoming",
                    "party_id": 2,
                    "race_id": 28,
                    "slug": "rebecca-rhynhart"
                }
            ],
            "candidate_order": 0,
            "district": 0,
            "fec_id": "",
            "id": 94,
            "is_active": 1,
            "name_first": "Rebecca Rhynhart",
            "name_last": "",
            "name_middle": "",
            "name_suffix": "",
            "slug": "rebecca-rhynhart",
            "social_blob": "",
            "total_money_donated": 79519.65,
            "total_money_in_pa": 0,
            "total_money_in_philly": 0,
            "total_money_out_pa": 0,
            "total_money_spent": 0,
            "website": ""
        },
        {
            "candidacies": [
                {
                    "candidacy_order": 4,
                    "candidacy_type": "challenger",
                    "candidate_id": 96,
                    "id": 4,
                    "outcome": "upcoming",
                    "party_id": 1,
                    "race_id": 28,
                    "slug": "michael-tomlinson"
                }
            ],
            "candidate_order": 0,
            "district": 0,
            "fec_id": "",
            "id": 96,
            "is_active": 1,
            "name_first": "Michael Tomlinson",
            "name_last": "",
            "name_middle": "",
            "name_suffix": "",
            "slug": "michael-tomlinson",
            "social_blob": "",
            "total_money_donated": 712.0,
            "total_money_in_pa": 0,
            "total_money_in_philly": 0,
            "total_money_out_pa": 0,
            "total_money_spent": 0,
            "website": ""
        }
    ],
    "metadata": {}
}

Based on the logic which performs retrieval from the database and the definition of the Candidacy object itself I would expect the "party" field to be present on each object in the "candidacies" array. In the sample request however, it is not.

lottspot commented 6 years ago

This issue blocks progress on #39

lottspot commented 6 years ago

Also, is this actually a bug, or am I just doing this wrong/expecting the wrong things?

lottspot commented 6 years ago

Hopped into a shell and played with the code directly on the python CLI, which turned in some interesting results. First, I retrieved the query object similarly to how the /candidates endpoint does so.

>>> from leverageapi import models
>>> s = models.session
>>> rid = 28
>>> m1 = models.Candidate
>>> m2 = models.Candidacy
>>> q = s.query(m1).join(m1.candidacies).filter(m2.race_id==rid)

Then I checked on whether the candidacies relationship is actually loading, as is indicated that it should in the models definition.

>>> o = list(q)
>>> o[0].candidacies
[<Candidacy 'Rebecca Rhynhart' '', (2017 'general')>]
>>> o[0].candidacies[0].party.party_name
'Democratic'

So the relationship is there after the query is completed; it just isn't being passed through to the object that the endpoint actually returns. This makes me believe that this issue is being caused by the implementation of the Candidacy.as_dict method, which is responsible for creating the object representation returned to API clients.

Interesting that the string 'candidacies' fails the test it would need to be included in the object returned to the API client

>>> 'candidacies' in o[0].__table__.columns
False
lottspot commented 6 years ago

The problem of serializing nested sqlalchemy objects to JSON is not trivial as it turns out. Potential solutions I've researched so far include:

lottspot commented 6 years ago

Also, it seems like maybe sqlalchemy collections offer a route to get there? If they are a possible route, it seems like the most complex one.

lottspot commented 6 years ago

I was able to basically understand the JSON encoder solution in a single reading of it, so I think it's probably the first solution I'll attempt to implement.