Package: factual
This package wraps theFactual v3
API.
Its query style is SQLAlchemy-inspired and designed to make it easy to
build “read” requests by chaining filter calls together.There is also limited support for v2 API, including actions other than
“read,” under the “v2” subpackage.Note that there are minor API inconsistencies between the v2 and v3
versions, both on the server side and in this wrapper. The v3 version of
the wrapper is now the default.API accessTo get started, you’ll need toregister for Factual API OAuth
credentials.UsageFirst, create a Session using your v3 API OAuth consumer key and
consumer secret:import factual
session = factual.Session(
                         consumer_key="myOAuthConsumerKey",
                         consumer_secret="myOAuthConsumerSecret"
                         )Now, build a query using thereadaction on the"global"table:query = session.read("global")You can apply as many filters as you’d like to the query. Filters on a
query are cumulative, and can be chained:query.search("coffee")
query.filter({"locality": "Albany"}).filter({"region":"NY"})When you’re done,run()the query and retrieve the results using therecords()method:from pprint import pprint

data = query.run().records()
pprint(data)Geographic queriesYou probably want to search for places near a point. Use thewithin(latitude, longitude, radius_in_meters)helper method ofread.within()chains and applies like any other filter, except
that the last call will overwrite earlier geo filters. The underlying
Factual filter API has changed between v2 and v3, but this will work for
both:query = session.read("global").within(40.7353,-73.9912,1000).search("coffee")PaginationGet more results usingcount()andpage():query.count(30).page(2) # 30 results per request, second page of resultsNote that Factual’s API instead useslimitandoffset, so I
should probably change the wrapper to match.CategoriesFactual provides categories as hierarchical strings. That is, any place
marked “Food & Beverage > Bakeries” is in the “Bakeries” subcategory of
“Food & Beverage.”It’s possible to query for either specific subcategories or parent
categories using the$bw(“begins with”) filter operator. You can
then search across multiple of these$bwfilters by chaining them
together with$or.Because this can get pretty lengthy, thecategory_helpersmodule has
amake_category_filterfunction.make_category_filtertakes a
list of category strings and combines them into a$bw/orfilter.
Since$bwwill always include all subcategories of an supercategory
listed,make_category_filteralso dedupes the provided categories to
generate the shortest list possible. This may mean that it will include
more subcategories than you indended; but if you want to get in to$and $not $bwtangles, you’re on your own.Passblank = Trueas a kwarg tomake_category_filterif you also
want results without categories set.from factual import category_helpers

my_categories = [ "Food & Beverage", "Food & Beverage > Bakeries", "Shopping" ]
my_filters = category_helpers.make_category_filter(my_categories, blank=True)
# {'$or': ({'category': {'$bw': 'Food & Beverage'}}, {'category': {'$bw': 'Shopping'}}, {'category': {'$blank': True}})}

query = s.read("global").filter(my_filters)Non-OAuth requestsIt’s possible to skip OAuth for the v3 API, if, for instance, there’s
some trouble with signing requests. Creating a Session without aconsumer_secretwill authenticate requests via theKEYquery
string parameter rather than OAuth:non_oauth_session = factual.Session(consumer_key="myOAuthConsumerKey")Note that Factualdiscourages falling back to the KEY
parameter,
and intends it for debugging use only, so use OAuth if possible.Examplesfrom factual import *
s = Session(consumer_key="deadbeef", consumer_secret="foobar")
my_place = s.read("global").search("coffee").run().records()[0]Building requests one piece at a time:query = s.read("global")
query.filter({"name": "Foobar"})
if my_address != None:
    query.filter({"address": my_address})
response = query.run()
records = response.records()Limiting categories and using the filter helper functions:from factual.filter_helpers import *
q = s.read("global").filter(
                    or_(
                        bw_("category", "Food"),
                        bw_("category", "Arts")
                    )
                  ).search("Foobar")
records = q.run().records()Geo queries:# lat, lon, radius in meters
coffee_places = s.read(places).search("coffee").within(40.7353,-73.9912,1000).run().records()Factual v2 “Server” APITo use the v2 API, instantiate a factual.v2.Session object instead of a
factual.Session object:import factual
v2_session = factual.v2.Session(api_key="deadbeef")
...Note that you’ll need to provide a v2 API key using theapi_keyargument. Factual issues v2 API keys via a different process than for v3
API credentials.In the v2 API, you can also modify a record in the Playpen:v2_session = factual.v2.Session(api_key="deadbeef")
p = v2_session.read(USLocalPlaypen).search("coffee").count(1).run().records()[0]
p['address'] += "/Foobar"
v2_session.input(USLocalPlaypen).values(p).comment("Silly update test").run()See also the Python documentation for session.Session and requests.Read
andFactual’s developer
documentation.TODOWrite support for v3, when availableMultiple search filters (search filters currently replace one
another)Changelogv0.1.2 - Add async option toRequest.run()to permit delaying the
processing of the HTTP response. If the asynchttp module is
installed, this will cause the initial call torun(async=True)to
immediately return aget_responsefunction, allowing you to defer
the blocking call until the results are needed.