43
Lessons learnt building @RossC0 http://github.com/rozza

Pyconie 2012

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Pyconie 2012

Lessons learnt building

@RossC0http://github.com/rozza

Page 2: Pyconie 2012

WHAT IS MONGODB?

A document database

Highly scalable

Developer friendly

http://mongodb.org

In BSON

{ _id : ObjectId("..."), author : "Ross", date : ISODate("2012-07-05..."), text : "About MongoDB...", tags : [ "tech", "databases" ], comments : [{ author : "Tim", date : ISODate("2012-07-05..."), text : "Best Post Ever!" }], comment_count : 1}

Page 3: Pyconie 2012

WHAT IS MONGODB?

In Python In BSON

{ "_id" : ObjectId("..."), "author" : "Ross", "date" : datetime(2012,7,5,10,0), "text" : "About MongoDB...", "tags" : ["tech", "databases"], "comments" : [{ "author" : "Tim", "date" : datetime(2012,7,5,11,35), "text" : "Best Post Ever!" }], "comment_count" : 1}

{ _id : ObjectId("..."), author : "Ross", date : ISODate("2012-07-05..."), text : "About MongoDB...", tags : [ "tech", "databases" ], comments : [{ author : "Tim", date : ISODate("2012-07-05..."), text : "Best Post Ever!" }], comment_count : 1}

Page 4: Pyconie 2012

http://education.10gen.com

Want to know more?

Page 5: Pyconie 2012

http://www.flickr.com/photos/51838104@N02/5841690990W H Y D O Y O U E V E N N E E D A N O D M ?

Page 6: Pyconie 2012

MongoDB a good fit

Documents schema in codeEnforces schemaData validationSpeeds up developmentBuild tooling off itCan DRY up code...

SCHEMA LESS != CHAOS

Page 7: Pyconie 2012

Inspired by Django's ORM

Supports Python 2.5 - Python 3.3

Originally authored by Harry Marr 2010

I took over development in May 2011

Current release 0.7.5

http://github.com/MongoEngine/mongoengine

Page 8: Pyconie 2012

INTRODUCING MONGOENGINE

class Post(Document): title = StringField(max_length=120, required=True) author = ReferenceField('User') tags = ListField(StringField(max_length=30)) comments = ListField(EmbeddedDocumentField('Comment'))

class Comment(EmbeddedDocument): content = StringField() name = StringField(max_length=120)

class User(Document): email = StringField(required=True) first_name = StringField(max_length=50) last_name = StringField(max_length=50)

Page 9: Pyconie 2012

CREATING A MODEL

class Post(Document): title = StringField(max_length=120, required=True) author = ReferenceField('User') tags = ListField(StringField(max_length=30)) comments = ListField(EmbeddedDocumentField('Comment'))

Define a class inheriting from Document

Map a field to a defined data type strings, ints, binary, files, lists etc..

By default all declared fields aren't required

Pass keyword arguments to apply constraints eg set if unique, max_length, default values.

Page 10: Pyconie 2012

INSERTING DATA

# Pass data into the constructoruser = User(email="[email protected]", name="Ross").save()

# Create instance and edit / update in placepost = Post()post.title = "mongoengine"post.author = userpost.tags = ['odm', 'mongodb', 'python']post.save()

Create instance of the object

Update its attributes

Call save, insert, update to persist the data

Page 11: Pyconie 2012

QUERYING DATA

# An `objects` manager is added to every `Document` classusers = User.objects(email='[email protected]')

# Pass kwargs to commands are lazy and be extended as neededusers.filter(auth=True)

# Iterating evaluates the querysetprint [u for u in users]

Documents have a queryset manager (objects) for querying

You can continually extend it

Queryset evaluated on iteration

Page 12: Pyconie 2012

6 LESSONS LEARNT

Page 13: Pyconie 2012

LE S S O N 1 : D I V E I N !http://www.flickr.com/photos/jackace/565857899/

Page 14: Pyconie 2012

In May 2011

>200 forks>100 issues~50 pull requests

I needed it

Volunteered to helpStarted reviewing issuesSupported Harry and community

PROJECT STALLED

Page 15: Pyconie 2012

LE S S O N 2 : M E TA C L A S S E Shttp://www.flickr.com/photos/ubique/135848053

Page 16: Pyconie 2012

WHATS NEEDED TO MAKE AN ORM?

Instance methods

validation datamanipulate dataconvert data to and from mongodb

Queryset methods

Finding dataBulk changes

Page 17: Pyconie 2012

METACLASSES

class Document(object): __metaclass__ = TopLevelDocumentMetaclass ...

class EmbeddedDocument(object): __metaclass__ = DocumentMetaclass ...

Needed for:

1. inspect the object inheritance

2. inject functionality to the class

Its surprisingly simple - all we need is: __new__

Page 18: Pyconie 2012

METACLASSES 101

TopLevelDocument

Document

python's type

cls, name, bases, attrs

IN

new class

Out

Page 19: Pyconie 2012

METACLASSES

TopLevelDocument

Document

python's type

Creates default meta datainheritance rules, id_field, index information, default ordering.Merges in parents metaValidationabstract flag on an inherited class collection set on a subclass

Manipulates the attrs going in.

IN

Page 20: Pyconie 2012

METACLASSES

TopLevelDocument

Document

python's type

Merges all fields from parentsAdds in own field definitionsCreates lookups_db_field_map_reverse_db_field_mapDetermine superclasses (for model inheritance)

IN

Page 21: Pyconie 2012

METACLASSES

TopLevelDocument

Document

python's type

Adds in handling for delete rulesSo we can handle deleted References

Adds class to the registrySo we can load the data into the correct class

OUT

Page 22: Pyconie 2012

METACLASSES

TopLevelDocument

Document

python's type

Builds index specificationsInjects queryset managerSets primary key (if needed)

OUT

Page 23: Pyconie 2012

LESSONS LEARNT

Spend time learning what is being done and why

Don't continually patch:

Rewrote the metaclasses in 0.7

Page 24: Pyconie 2012

LE S S O N 3 : S T R AY I N G F R O M T H E PAT Hhttp://www.flickr.com/photos/51838104@N02/5841690990

Page 25: Pyconie 2012

REWRITING THE QUERY LANGUAGE

# In pymongo you pass dictionaries to queryuk_pages = db.page.find({"published": True})# In mongoengineuk_pages = Page.objects(published=True)

# pymongo dot syntax to query sub documentsuk_pages = db.page.find({"author.country": "uk"})# In mongoengine we use dunder insteaduk_pages = Page.objects(author__country='uk')

Page 26: Pyconie 2012

REWRITING THE QUERY LANGUAGE

#Somethings are nicer - regular expresion searchdb.post.find({'title': re.compile('MongoDB', re.IGNORECASE)})Post.objects(title__icontains='MongoDB') # In mongoengine

# Chaining is nicerdb.post.update({"published": False}, {"$set": {"published": True}}, multi=True)

Post.objects(published=False).update(set__published=True)

Page 27: Pyconie 2012

LE S S O N 4 : N OT A LL I D E A S A R E G O O Dhttp://www.flickr.com/photos/abiding_silence/6951229015

Page 28: Pyconie 2012

CHANGING SAVE# In pymongo save replaces the whole documentdb.post.save({'_id': 'my_id', 'title': 'MongoDB', 'published': True})

# In mongoengine we track changespost = Post.objects(_id='my_id').first()post.published = Truepost.save()

# Results in:db.post.update({'_id': 'my_id'}, {'$set': {'published': True}})

Page 29: Pyconie 2012

CHANGING SAVE

Any field changes are noted

How to monitor lists and dicts?

Custom List and Dict classes

Observes changes and marks as dirty

Page 30: Pyconie 2012

HOW IT WORKS

class Post(Document): title = StringField(max_length=120, required=True) author = ReferenceField('User') tags = ListField(StringField(max_length=30)) comments = ListField(EmbeddedDocumentField('Comment'))

class User(Document): email = StringField(required=True) first_name = StringField(max_length=50) last_name = StringField(max_length=50)

class Comment(EmbeddedDocument): content = StringField() name = StringField(max_length=120)

Page 31: Pyconie 2012

Post

HOW IT WORKS

- comments

comment

comment

comment

post = Post.objects.first()post.comments[1].name = 'Fred'post.save()

Page 32: Pyconie 2012

Post

HOW IT WORKS

- comments

comment

1.Convert the comments data to a BaseList

BaseList Stores the instance and name / location

comment

comment

post.comments[1].name = 'Fred'

Page 33: Pyconie 2012

Post

HOW IT WORKS

- comments

comment

2.Convert the comment data to BaseDict

sets name as: 'comments.1'

comment

comment

post.comments[1].name = 'Fred'

Page 34: Pyconie 2012

Post

HOW IT WORKS

- comments

comment

3.Change name to "Fred"

4. Tell Post 'comments.1.name' has changed

comment

comment

post.comments[1].name = 'Fred'

Page 35: Pyconie 2012

Post

HOW IT WORKS

- comments

comment

5.On save()Iterate all the changes on post and run $set / $unset queries

comment

comment

post.save()

db.post.update( {'_id': 'my_id'}, {'$set': { 'comments.1.name': 'Fred'}})

Page 36: Pyconie 2012

A GOOD IDEA?

+ Makes it easier to use

+ save acts how people think it should

- Its expensive

- Doesn't help people understand MongoDB

Page 37: Pyconie 2012

LE S S O N 5 : M A N A G I N G A C O M M U N I T Yhttp://kingscross.the-hub.net/

Page 38: Pyconie 2012

Github effect

>10 django mongoengineprojectsNone active on pypiLittle cross project communication

CODERS JUST WANT TO CODE *

* Side effect of being stalled?

Page 39: Pyconie 2012

Flask-mongoengine on pypi There were 2 different projectsNow has extra maintainers from the flask-mongorest

Django-mongoengine*Spoke to authors of 7 projects and merged their work together to form a single library

* Coming soon!

REACH OUT

Page 40: Pyconie 2012

THE COMMUNITY

Not all ideas are good!

Vocal people don't always have great ideas

Travis is great* * but you still have to read the pull request

Communities have to be managedI've only just started to learn how to herding cats

Page 41: Pyconie 2012

LE S S O N 6 : D O N ' T B E A F R A I D TO A S Khttp://www.flickr.com/photos/kandyjaxx/2012468692

Page 42: Pyconie 2012

WebsiteDocumentationTutorialsFramework supportCore mongoengine

http://twitter.com/RossC0

I NEED HELP ;)

http://github.com/MongoEngine

Page 43: Pyconie 2012

Q U E S T I O N S ?http://www.flickr.com/photos/9550033@N04/5020799468