Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring stuff up to snuff #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 142 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,67 @@
# Booli API

A simple and powerful python wrapper for the Booli.se API

# Setting up
## Setting up

```python
from booliapi import BooliAPI
Booli = BooliAPI("your username", "your api key")
```

For convenience, these can also be stored in `~/.boolirc` as JSON:

from booliapi import BooliAPI
Booli = BooliAPI("your username", "your api key")
```json
{"caller_id": "your username", "key": "your api key"}
```

# Searching
Then simply omit the arguments, so `Booli = BooliAPI()`.

## Searching

`Booli.search()` performs a search and returns a list of `Listing`s.
Here's how you specify your search parameters:

## Searching by city and/or neighborhood
### Searching by city and/or neighborhood

To search for listings in a given city or neighborhood, pass a string
as the first argument to `.search()`.

Booli.search("Uppsala") # city
```python
Booli.search("Uppsala") # city
Booli.search("Uppsala/Luthagen") # city + neighborhood
```

#### Example: Printing a nice list of apartments in Uppsala's "Fålhagen" neighborhood.

Booli.search("Uppsala/Luthagen") # city + neighborhood
```python
for listing in Booli.search("Uppsala/Fålhagen", typ=u"lägenhet"):
print "%s, %s" % (listing.address, listing.neighborhood)
print " %s rum; %d kr, %d kr/mån" % (listing.rooms_as_text,
listing.price,
listing.fee)
```

### Example: Printing a nice list of apartments in Uppsala's "Fålhagen" neighborhood.

for listing in Booli.search("Uppsala/Fålhagen", typ=u"lägenhet"):
print "%s, %s" % (listing.address, listing.neighborhood)
print " %s rum; %d kr, %d kr/mån" % (listing.rooms_as_text,
listing.price,
listing.fee)

Result:

Hjalmar Brantingsgatan 9B, Fålhagen
2 rum; 1490000 kr, 2710 kr/mån
Torkelsgatan 8C, Fålhagen
1 rum; 1075000 kr, 2563 kr/mån
Petterslundsgatan 33, Fålhagen
1 rum; 1050000 kr, 2032 kr/mån
...
```
Hjalmar Brantingsgatan 9B, Fålhagen
2 rum; 1490000 kr, 2710 kr/mån
Torkelsgatan 8C, Fålhagen
1 rum; 1075000 kr, 2563 kr/mån
Petterslundsgatan 33, Fålhagen
1 rum; 1050000 kr, 2032 kr/mån
...
```

## Listings within distance of a point
### Listings within distance of a point

# Listings within 1 km from Booli's Uppsala office
listings = Booli.search(centerLat=59.8569131, centerLong=17.6359056, radius=1)
```python
# Listings within 1 km from Booli's Uppsala office
listings = Booli.search(centerLat=59.8569131, centerLong=17.6359056, radius=1)
```

## More specific searching
### More specific searching

Any keyword arguments you pass to `.search()` will simply be used as URL
parameters in the API query. Unicode strings, integers and floats are
Expand All @@ -54,15 +73,16 @@ also valid keyword arguments here.

[API Documentation]: http://www.booli.se/api/docs/

Booli.search("Uppsala", pris="0-1500000", typ="lägenhet")
Booli.search("Stockholm/Södermalm", rum=[1,2])
Booli.search("Stockholm", typ=["villa", "radhus"], rum=4)
```python
Booli.search("Uppsala", pris="0-1500000", typ="lägenhet")
Booli.search("Stockholm/Södermalm", rum=[1,2])
Booli.search("Stockholm", typ=["villa", "radhus"], rum=4)
```

It also means that the names of these parameters are all Swedish. But
then again, so are you, probably. Puss på dig.


# Listings
## Listings

The objects we're dealing with are called `Listing`s. The data about
each listing is stored as attributes.
Expand All @@ -74,13 +94,13 @@ each listing is stored as attributes.
- `neighborhood` and `city` - exactly what you think
- `rooms` - number of rooms
- `size` - square meterage
- `lot_size` - size of the lot
- `lot\_size` - size of the lot
- `price` - the listed sales price
- `fee` - monthly fee

*Note:* The number of rooms is actually a float, since some listings
are specified as having for example 2.5 rooms. There is a special
property called `rooms_as_text` that gives you a nicer string
property called `rooms\_as\_text` that gives you a nicer string
representation.

**Additional geographic data**
Expand All @@ -92,21 +112,21 @@ each listing is stored as attributes.
**Metadata**

- `url` - the url to this listing on booli.se
- `image_url` - the url to a thumbnail image, if available
- `image\_url` - the url to a thumbnail image, if available
- `agency` - the name of the real estate agency representing the seller
- `created` - when this listing was created
- `id` - internal Booli ID


# Filtering, sorting and grouping
## Filtering, sorting and grouping

It's super-easy to filter and sort the listings.

The list you get from `.search()` is actually a clever subclass of
`list`, called `ResultSet`. If you're familiar with Django's QuerySet
API, you'll like this.

## Filtering
### Filtering

`ResultSet.filter(**kwargs)` and `ResultSet.exclude(**kwargs)`

Expand All @@ -122,24 +142,28 @@ If you specify multiple parameters, they are combined using boolean `and`.
One way to use these methods is to do more specific filtering than the
API itself supports.

# Get only listings from a specific agency
listings = Booli.search("Uppsala").filter(agency=u"Widerlöv & Co")
```python
# Get only listings from a specific agency
listings = Booli.search("Uppsala").filter(agency=u"Widerlöv & Co")
```

Another way is to work on different subsets of a search result without
having to make another API call.

listings = Booli.search("Uppsala", typ=u"lägenhet")
```python
listings = Booli.search("Uppsala", typ=u"lägenhet")

for listing in listings.filter(neighborhood=u"Fålhagen"):
# do something
for listing in listings.filter(neighborhood=u"Fålhagen"):
# do something

for listing in listings.filter(neighborhood=u"Luthagen"):
# do something else
for listing in listings.filter(neighborhood=u"Luthagen"):
# do something else
```python

Filtering ResultSets never affects the underlying API calls; it only
creates a filtered copy the results you've already fetched.

## Filter operators
### Filter operators

Just as in Django's QuerySets, you can do more than just exact
matching. When you type `.filter(attr=value)`, it's actually
Expand All @@ -157,51 +181,59 @@ Here are the operators and their plain-python equivalents:
- `attr__contains=value` - `value in attr`
- `attr__startswith=value` - `attr.startswith(value)`
- `attr__endswith=value`- `attr.endswith(value)`
- `attr__range=(start, end)` - `start <= attr <= end`
- `attr__range=(start, end)` - `start &lt;= attr &lt;= end`

There's also `iexact`, `icontains`, `istartswith` and `iendswith`,
which are case-insensitive variants of their i-less buddies.

### Example: Finding all apartments on a specific street in Uppsala

apts = Booli.search("Uppsala", typ=u"lägenhet")
#### Example: Finding all apartments on a specific street in Uppsala

apts.filter(address__startswith="Storgatan")
```python
apts = Booli.search("Uppsala", typ=u"lägenhet")
apts.filter(address__startswith="Storgatan")
```

### Example: Getting listings in any of several neighborhoods
#### Example: Getting listings in any of several neighborhoods

apts.filter(neighborhood__in=[u"Luthagen", u"Centrum"])
```python
apts.filter(neighborhood__in=[u"Luthagen", u"Centrum"])
```

### Example: Getting listings from one agency, excluding a neighborhood
#### Example: Getting listings from one agency, excluding a neighborhood

apts.filter(agency=u"Riksmäklaren").exclude(neighborhood=u"Sävja")
```python
apts.filter(agency=u"Riksmäklaren").exclude(neighborhood=u"Sävja")
```

### Example: Getting listings published in the last 8 hours
#### Example: Getting listings published in the last 8 hours

from datetime import datetime, timedelta
eight_hours_ago = datetime.now() - timedelta(hours=8)
```python
from datetime import datetime, timedelta
eight_hours_ago = datetime.now() - timedelta(hours=8)

for listing in apts.filter(created__gt=eight_hours_ago)
# do something
for listing in apts.filter(created__gt=eight_hours_ago)
# do something
```

## Sorting
### Sorting

`ResultSet.order_by(*attributes)`

`order_by()` takes one or more strings that specify which attributes
to sort by. It returns a new ResultSet.

# Sort by address
apts.order_by("address")
```python
# Sort by address
apts.order_by("address")

# Sort by price, descending (most expensive first)
apts.order_by("-price")
# Sort by price, descending (most expensive first)
apts.order_by("-price")

# Sort by neighborhood first, then by price descending
apts.order_by("neighborhood", "-price")
# Sort by neighborhood first, then by price descending
apts.order_by("neighborhood", "-price")
```


## Grouping
### Grouping

`ResultSet.group_by(attribute, [count_only=False])`

Expand All @@ -215,28 +247,32 @@ group's resultset instead of the actual resultset.

In almost all cases, you should sort your resultset before grouping.

### Example: Top 5 Agencies in Södermalm, Stockholm
#### Example: Top 5 Agencies in Södermalm, Stockholm

results = Booli.search("Stockholm/Södermalm").order_by("broker").group_by("broker")
results.sort(key=lambda x: len(x[1]), reverse=True)
for broker, listings in results[:5]:
print "%s (%d listings)" % (broker, len(listings))
print
other = sum(len(listings) for (broker, listings) in results[5:])
print u"Other: %d listings" % (other,)
```python
results = Booli.search("Stockholm/Södermalm").order_by("broker").group_by("broker")
results.sort(key=lambda x: len(x[1]), reverse=True)
for broker, listings in results[:5]:
print "%s (%d listings)" % (broker, len(listings))
print
other = sum(len(listings) for (broker, listings) in results[5:])
print u"Other: %d listings" % (other,)
```

Result:

Fastighetsbyrån (29 listings)
Svensk Fastighetsförmedling (25 listings)
Erik Olsson Fastighetsförmedling (17 listings)
Södermäklarna (13 listings)
Notar (12 listings)

Other: 90 listings
```
Fastighetsbyrån (29 listings)
Svensk Fastighetsförmedling (25 listings)
Erik Olsson Fastighetsförmedling (17 listings)
Södermäklarna (13 listings)
Notar (12 listings)

Other: 90 listings
```


## Complex filtering - Q and F objects
### Complex filtering - Q and F objects

When you provide more than one parameter to `filter` or `exclude` they
are combined using boolean AND. If that's not good enough for you,
Expand All @@ -249,39 +285,49 @@ They can be combined using the `&` and `|` operators, and negated
using `~`. These operations yield new Q objects representing the
combined filter condition.

### Example: Finding apartments on any of several streets
#### Example: Finding apartments on any of several streets

kungsgatan = Q(address__startswith="Kungsgatan")
storgatan = Q(address__startswith="Storgatan")
Booli.search("Uppsala").filter(kungsgatan | storgatan)
```python
kungsgatan = Q(address__startswith="Kungsgatan")
storgatan = Q(address__startswith="Storgatan")
Booli.search("Uppsala").filter(kungsgatan | storgatan)
```

`F(attribute)`

If you want to use a listing attribute as the right-hand side in a
comparison, you have to use `F` objects.

### Example: Exploring the difference between City and Municipality in the data
#### Example: Exploring the difference between City and Municipality in the data

Booli.search("Uppsala").exclude(city=F("municipality"))
```python
Booli.search("Uppsala").exclude(city=F("municipality"))
```

`F` objects are very similar to `operator.attrgetter`, but with the
addition that they can be combined with other `F` objects as well as
with constants.

### Example: For rural living, try listings where the address is neighborhood + " "
#### Example: For rural living, try listings where the address is neighborhood + " "

(+ number, but we can't search for that)

Booli.search("Uppsala").filter(address__startswith=F("neighborhood") + " ")
```python
Booli.search("Uppsala").filter(address__startswith=F("neighborhood") + " ")
```

### Example: Grand living - finding a house where the lot is at least fifty times the size of the house
#### Example: Grand living - finding a house where the lot is at least fifty times the size of the house

(avoiding those that oddly have size=0)

Booli.search("Uppsala", typ="villa").exclude(size=0) \
.filter(lot_size__gte=F("size")*50)
```python
Booli.search("Uppsala", typ="villa").exclude(size=0) \
.filter(lot_size__gte=F("size")*50)
```

### Example: Filtering apartments that cost less than 25000/sqm
#### Example: Filtering apartments that cost less than 25000/sqm

Booli.search("Uppsala", typ=u"lägenhet").filter(price__lt=F("size")*25000)
```python
Booli.search("Uppsala", typ=u"lägenhet").filter(price__lt=F("size")*25000)
```

Loading