Welcome to SearchableCollection’s documentation!

https://travis-ci.org/joranbeasley/searchable_collection.svg?branch=master https://coveralls.io/repos/github/joranbeasley/searchable_collection/badge.svg?branch=master https://api.codacy.com/project/badge/Grade/870c467e59784d86bd598a5d5d928bbd

I wrote SearchableCollections in order to provide an ORM like interface to regular lists

Requirements

  • Python2.6, Python2.7, or Python3

Installation

setup.py install
or pip install .
or install it from pipy with pip install searchable_collection
or directly from github pip install git+https://github.com/joranbeasley/searchable_collection.git

Examples

examples using python primatives

Creating A SearchableList

you can create a list just like a normal list (well mostly)

1
2
3
4
from searchable_collection import SearchableCollection
some_other_list = [1,2,3,4,5,6]
my_list = SearchableCollection(some_other_list)
print(list(my_list.find_all_where(in=[4,5])))

or you can simply append items as needed

1
2
3
4
5
6
from searchable_collection import SearchableCollection
some_other_list = [1,2,3,4,5,6]
my_list = SearchableCollection()
for i in some_other_list:
    my_list.append(i)
print(list(my_list.find_all_where(in=[2,6])))

or you can use extend

from searchable_collection import SearchableCollection some_other_list = [1,2,3,4,5,6] my_list = SearchableCollection() my_list.extend(some_other_list) print(list(my_list.find_all_where(in=[2,6])))

What can go in a Searchable Collection?

well pretty much anything... and it should just work, originally it was designed specifically with classes in mind, however it should really work just fine with anything

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
original_data = [[1,2,3],[3,4,5,'e'],{"w":7},"pie","apple",{"e":67},1,2,3,4,5,6]
my_list = SearchableCollection(original_data)

print(list(my_list.find_all_where(e=67))
print(list(my_list.find_all_where(contains="e"))
print(list(my_list.find_all_where(contains=2))
print(list(my_list.find_all_where(contains=3))

# do an re.match (only matches "pie")
print(list(my_list.find_all_where(match="p.e"))
# do an re.search (matches both "pie" and "apple")
print(list(my_list.find_all_where(search="p.e"))

it starts getting even more interesting with nested dictionaries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
my_list = SearchableCollection()
my_list.append({"sub_dict":{"anumber":56,"aword":"apple","alist":[1,2,3]})
my_list.append({"sub_dict":{"anumber":26,"aword":"pineapple","alist":[7,8,9]})
my_list.append({"sub_dict":{"anumber":126,"aword":"orange","alist":[7,18,19]})

# d['sub_dict']['anumber'] == 26
print(list(my_list.find_all_where(sub_dict__anumber=26))

# d['sub_dict']['anumber'] > 50
print(list(my_list.find_all_where(sub_dict__anumber_gt=50))

# d['sub_dict']['aword'] == "orange"
print(list(my_list.find_all_where(sub_dict__aword="orange"))

# "n" in d['sub_dict']['aword']
print(list(my_list.find_all_where(sub_dict__aword__contains="n"))

# d['sub_dict']['aword'].endswith("le")
print(list(my_list.find_all_where(sub_dict__aword__endswith="le"))

# 3 in d['sub_dict']['alist']
print(list(my_list.find_all_where(sub_dict__alist__contains=3))

What Modifiers Can I Use

the complete list of modifiers is as follows

__contains   -  x in y
__in         -  y in x # note that if the field is ommited it is replaced with is_in `...where(is_in=...)`
__startswith -  x.startswith(y)
__endswith   -  x.endswith(y)
__search     -  re.search(y,x)
__match      -  re.match(y,x)
# numeric operators
__gt         -  x > y
__gte        -  x >= y
__lt         -  x < y
__lte        -  x <= y
__eq         -  x == y # in general this is the assumed operation and can be ommited

you can optionally negate any of the operators

__not_contains   -  x not in y
__not_in         -  y not in x
__not_startswith -  not x.startswith(y)
__not_endswith   -  not x.endswith(y)
__not_search     -  not re.search(y,x)
__not_match      -  not_re.match(y,x)
# numeric operators
__not_gt         -  not x > y  # or x <= y
__not_gte        -  not x >= y # or x < y
__not_lt         -  not x < y  # or x >= y
__not_lte        -  x <= y     # or x > y
__not_eq         -  x != y

SearchableCollection Usage Guide

Simple Access

you may access a Searchablelist exactly the same as a normal list for the most part

1
2
3
4
5
6
7
from searchable_collection import SearchableCollection
some_other_list = [1,2,3,4,5,6]
my_list = SearchableCollection(some_other_list)
print(my_list[2],my_list[-1])  # 3 and 6
print(len(my_list),my_list.pop(3),len(my_list))
my_list.append(5)
print(len(my_list),my_list[-1])

Adding Element To Searchable List

you should be able to add elements to a Searchable list the same as if it were a normal list

1
2
3
4
5
from searchable_collection import SearchableCollection

my_list = SearchableCollection()
my_list.append(4)
my_list.extend(["a",66,{'asd':'dsa','b':5}])

Searching For Elements

this is really the whole purpose of this module, to provide a flexible ORM like interface to searching though lists I doubt its super efficient, so i wouldnt recommend using it with huge lists, but it should be able to search a few hundred records near instantly

See also

Query Reference

SearchableCollection API Documentation

In General SearchableCollection attempts to mimic the functionality of a list exactly

that means you can do indexing like my_list[0], my_list[-1]

and you can also do slicing like my_list[5:15:3]

and you can do standard list setitems like my_list[6] = SomeClass()

you can also use the normal x in my_list operator

Available Methods

classmethod SearchableCollection.find_one_where(**query_conditions)
Parameters:query_conditions (SEE: QUERY ARGUMENTS) – keyword pairs that describe the current search criteria
Returns:A single match from the collection (the first match found), or None if no match is found

search the collection and return the first item that matches our search criteria

my_collection.find_one_where(sn="123123",in_use=False)
classmethod SearchableCollection.find_all_where(**query_conditions)
Parameters:query_conditions (SEE: QUERY ARGUMENTS) – keyword pairs that describe the current search criteria
Returns:all of the matches from the collection
Return type:generator

this will search the collection for any items matching the provided criteria

for result in my_collection.find_all_where(condition1=3, condition2=4):
    do_something(result)
classmethod SearchableCollection.delete_where(**query_conditions)
Parameters:query_conditions (SEE: QUERY ARGUMENTS) – keyword pairs that describe the current search criteria
Returns:None

Deletes any items in the collection that match the given search criteria

my_collection.delete_where(sn__startswith="AB") # delete all things that have a sn attribute starting with "AB"

Query Reference

Field lookups are how you specify the meat of a query. They’re specified as keyword arguments to the following SearchableCollection methods

Basic lookups **conditions arguments take the form <field>__<lookuptype>=value. (That’s a double-underscore).

For example:

>>> entry_objects.filter(pub_date__lte=datetime.now())

would find all the things in entry_objects where entry_object.pub_date <= now()*

**if entry_object is a dict it would find all entries where entry_object['pub_date'] <= now()

additionally you can negate any of the lookuptypes by prepending not_

>>> entry_objects.filter(pub_date__not_lte=datetime.now())

would find all entry_objects where entry_object.pub_date IS NOT less than or equal to now()

  • you **do not* have to supply both the field and the lookuptype*
  • if you ommit the lookuptype*, it will default to* eq
  • if you ommit the field, it will default to the root level object
  • if you ommit either, you do not need the double underscore( __ )

Query LookupType Reference

eq

tests a field for equality, this is the default lookuptype if None is specified

>>> entry_objects.find_all_where(serial_number__eq="SN123123")
>>> entry_objects.find_all_where(serial_number="SN123123")

are both equivelent statements, however when using the negated form you must specify eq

>>> entry_objects.find_all_where(serial_number__not_eq="SN123123")

is the negated form.

String LookupTypes
contains

tests a field to see if it contains a value (or substring)

>>> author_objects.find_all_where(articles_id_list__contains=15)

would return all the author_objects, that had field named articles_id_list, that contained the article_id of 15

>>> author_objects.find_all_where(articles_id_list__not_contains=15)

would return all the author_objects, that had field named articles_id_list, that DID NOT contain the article_id of 15

in

tests a field for membership in a set.

>>> entry_objects.find_all_where(status__in=["PENDING","ACTIVE"])
>>> entry_objects.find_all_where(status__not_in=["CANCELLED","FAILED"])

note: if you ommit the field you must access this as is_in

>>> entry_objects.find_all_where(is_in=[1,3,7,9])
startswith

tests a field for startswith

>>> entry_objects.find_all_where(serial_number__startswith("SN76"))

finds all the objects with a serial_number attribute that starts with "SN79"

>>> entry_objects.find_all_where(serial_number__not_startswith("SN76"))

finds all the objects that DO NOT have a serial_number attribute that starts with "SN79"

endswith

tests a field for endswith

>>> entry_objects.find_all_where(serial_number__endswith("3"))

finds all the objects with a serial_number attribute that ends with "3"

>>> entry_objects.find_all_where(serial_number__not_endswith("3"))

finds all the objects that DO NOT have a serial_number attribute that ends with "3"

tests a field for re.search, that is searches can appear anywhere in the target

>>> entry_objects.find_all_where(serial_number__search("3[0-9]"))

finds all the objects with a serial_number attribute that contains 3 followed by any digit

>>> entry_objects.find_all_where(serial_number__not_search("3[0-9]"))

finds all the objects that DO NOT have a serial_number attribute that contains 3 followed by any digit

match

tests a field for re.match, that is matches only match from the beginning

>>> entry_objects.find_all_where(serial_number__match("3[0-9]"))

finds all the objects with a serial_number attribute that starts with a 3 followed by any digit

>>> entry_objects.find_all_where(serial_number__not_match("3[0-9]"))

finds all the objects that DO NOT have a serial_number attribute that starts with a 3 followed by any digit

General LookupTypes
lt

less than

>>> entry_objects.find_all_where(cost__lt(3.50)) # x < 3.50
>>> entry_objects.find_all_where(cost__not_lt(3.50)) # x >= 3.50
lte

less than or equal

>>> entry_objects.find_all_where(cost__lte(3.50)) # x <= 3.50
>>> entry_objects.find_all_where(cost__not_lte(3.50)) # x > 3.50
gt

greater than

>>> entry_objects.find_all_where(rating__gt(9)) # x > 9
>>> entry_objects.find_all_where(rating__not_gt(9)) # x <= 9
gte

greater than or equal

>>> entry_objects.find_all_where(rating__gte(9)) # x >= 9
>>> entry_objects.find_all_where(cost__not_gte(9)) # x < 9

Lookups Than Span Sub-Objects

SearchableCollections offer a powerful and intuitive way to “follow” relationships in lookups, taking care of the search for you automatically, behind the scenes. To span a sub-object, just use the field name of sub-objects, separated by double underscores, until you get to the field you want.

>>> entry_objects.filter(blog__name='Beatles Blog')

this assumes you have an object with a field named “blog”, blog has a field named “name”

>>> entry = {"blog":{"name":...,"date":...,"author":{"name":...,"publications":[...]}}

this will locate the entry that has a blog, with a name field of “Beatles Blog”

This spanning can be as deep as you like

>>> entry_objects.filter(blog__author__name='Lennon')

Indices and tables