Skip to content

3. MongoDB for Python


1. MongoDB for Python 설치

  • PyMongo에 대한 자세한 내용은 공식 도큐먼트에서 확인할 수 있다.
  • MongoDB의 데이터 구조를 이해한 후 진행하면 좋다.


001

1) pymongo 설치

  • 다음과 같이 pymongo를 설치한다.


$(base) pip install pymongo


2) 포트 오픈 확인

  • 다음과 같이 27017번 포트가 열려있는지 확인한다.


$(base) netstat -nlpt | grep 27017
tcp        0      0 127.0.0.1:27017         0.0.0.0:*               LISTEN      5402/mongod


2. Making A Connection with MongoClient

  • PyMongo로 작업할 때의 첫 번째 단계는 실행 중인 mongod 인스턴스에 대해 MongoClient를 만드는 것이다.


from pymongo import MongoClient

client = MongoClient()


  • 위의 코드는 기본 호스트와 기본 포트에 연결한다.
  • 다음과 같이 호스트와 포트를 명시적으로 지정할 수 있다.


client = MongoClient("localhost", 27017)


  • 또는 MongoDB URI 형식으로 지정할 수 있다.


client = MongoClient("mongodb://localhost:27017")


3. Getting a Database

  • MongoDB의 단일 인스턴스는 여러 독립 Database를 지원할 수 있다.
  • PyMongo로 작업할 때 MongoClient 인스턴스에서 Attribute 스타일 액세스를 사용하여 Database에 액세스한다.


db = client.test_database


  • Attribute 스타일 액세스가 작동하지 않는 경우 Dictionary 스타일 액세스를 대신 사용할 수 있다.


db = client["test-database"]


4. Getting a Collection

  • Collection은 MongoDB에 저장된 Documents의 그룹이며 RDBMS의 테이블과 거의 동일하게 볼 수 있다.
  • PyMongo에서 Collection을 가져오는 것은 Database를 가져오는 것과 동일하게 작동한다.


collection = db.test_collection


  • Dictionary 스타일 액세스의 경우는 다음과 같다.


collection = db["test_collection"]


  • MongoDB의 Database 및 Collection은 Lazy 생성이다.
  • 즉, 위의 명령 중 어느 것도 MongoDB 서버에서 실제로 어떤 작업도 수행하지 않았다.
  • Database 및 Collection은 첫 번째 Document가 삽입될 때 생성된다.


5. Documents

  • MongoDB의 데이터는 JSON 스타일의 Documents를 사용하여 표현 및 저장된다.
  • PyMongo에서는 dict 타입을 사용하여 Documents를 나타낸다.


  • 예를 들어 다음 dict 타입을 사용하여 블로그 게시물을 나타낼 수 있다.


import datetime

post = {"author": "Mike",
        "text": "My first blog post!",
        "tags": ["mongodb", "python", "pymongo"],
        "date": datetime.datetime.utcnow()}


  • Documents에는 적절하게 BSON 타입으로 자동 변환되는 기본 Python 타입(datetime.datetime 인스턴스 등)이 들어갈 수 있다.


6. Inserting a Document

  • Document 하나를 Collection에 삽입하려면 insert_one() 메서드를 사용할 수 있다.


posts = db.posts
post_id = posts.insert_one(post).inserted_id

print(post_id)

# ObjectId('...')


  • Document가 삽입될 때 _id가 포함되어 있지 않은 경우 _id는 자동으로 추가된다.
  • _id 값은 Collection 전체에서 고유한 값이어야 한다.
  • insert_one() 메서드는 InsertOneResult의 인스턴스를 반환한다.


  • 첫 번째 Document를 삽입한 후 실제로 서버에는 게시물 Collection이 생성되었다.
  • Database의 모든 Collection을 나열하여 이를 확인할 수 있다.


print(db.list_collection_names())

# [u'posts']


7. Getting a Single Document With find_one()

  • MongoDB에서 수행할 수 있는 가장 기본적인 쿼리 유형은 find_one()이다.
  • 이 메서드는 쿼리와 일치하는 단일 Document를 반환하거나 일치하는 항목이 없는 경우 None을 반환한다.
  • 이 메서드는 일치하는 Document 하나를 찾거나 저장된 데이터들의 모양을 볼 때 간단하게 사용할 수 있다.


import pprint

pprint.pprint(posts.find_one())

# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'mongodb', u'python', u'pymongo'],
#  u'text': u'My first blog post!'}


  • 반환된 Document에는 삽입 시 자동으로 추가된 _id가 포함되어 있다.


  • 또한 find_one()은 결과 Document가 일치해야 하는 특정 요소에 대한 쿼리를 지원한다.
  • 다음과 같이 author"Mike"인 Document로 결과를 제한할 수 있다.


pprint.pprint(posts.find_one({"author": "Mike"})

# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'mongodb', u'python', u'pymongo'],
#  u'text': u'My first blog post!'}


  • author"Eliot"인 Document를 찾아볼 수도 있다.


posts.find_one({"author": "Eliot"})


  • 하지만 일치하는 특정 요소가 없기 때문에 아무것도 반환되지 않는다.


8. Querying By ObjectId

  • _id로 게시물을 찾을 수도 있다.


print(post_id)

# ObjectId('...')

pprint.pprint(posts.find_one({"_id": post_id}))

# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'mongodb', u'python', u'pymongo'],
#  u'text': u'My first blog post!'}


  • 이때 ObjectId는 문자열 표현과는 다른 인스턴스이다.


post_id_as_str = str(post_id)
print(posts.find_one({"_id": post_id_as_str}))

# None


  • 일반적으로 웹 애플리케이션의 작업은 Requests URL에서 ObjectId를 가져오고 일치하는 Document를 찾는 것일 것이다.
  • 이런 경우 find_one()에 전달하기 전에 문자열에서 ObjectId로 변환해야 한다.


from bson.objectid import ObjectId

def get(post_id):
    document = client.db.collection.find_one({"_id": ObjectId(post_id)})


  • Flask와 같은 웹 프레임워크를 사용할 때는 다음과 같이 항상 ObjectId로 변환해야 한다.


from pymongo import MongoClient
from bson.objectid import ObjectId
from flask import Flask, render_template

client = MongoClient()
app = Flask(__name__)

@app.route("/posts/<_id>")
def show_post(_id):
    post = client.db.posts.find_one({"_id": ObjectId(_id)})
    return render_template("post.html", post=post)

if __name__ == "__main__":
    app.run()


9. A Note On Unicode Strings

  • 일반적인 Python str 타입이 서버에서 검색될 때는 u'Mike'처럼 다르게 보인다.
  • 그 이유는 MongoDB는 데이터를 BSON 형식으로 저장한다.
  • BSON 문자열은 UTF-8로 인코딩되므로 PyMongo는 저장하는 모든 문자열이 유효한 UTF-8 데이터만 포함하도록 해야 한다.
  • 유니코드 문자열(<type 'unicode'>)은 먼저 UTF-8로 인코딩되어 서버로 입력되고, 검색될 때 다시 디코딩되기 때문이다.


10. Bulk Inserts

  • 단일 Document를 삽입하는 것 외에도 insert_many()의 첫 번째 인수로 list 타입을 전달하여 대량으로 삽입 작업을 수행할 수도 있다.


new_posts = [{"author": "Mike",
              "text": "Another post!",
              "tags": ["bulk", "insert"],
              "date": datetime.datetime(2009, 11, 12, 11, 14)},
             {"author": "Eliot",
              "title": "MongoDB is fun",
              "text": "and pretty easy too!",
              "date": datetime.datetime(2009, 11, 10, 10, 45)}]

result = posts.insert_many(new_posts)

print(result.inserted_ids)

# [ObjectId('...'), ObjectId('...')]


  • insert_many()의 결과는 이제 삽입된 각 Document에 대해 하나씩 총 두 개의 ObjectId 인스턴스를 반환한다.
  • new_posts[1]new_posts[0]과 서로 다른 점은 tags 필드가 없고 title 필드가 추가되었다는 것이다.
  • 이는 MongoDB가 Schema-free하다는 것을 의미한다.


11. Querying for More Than One Document

  • 쿼리 결과로 하나 이상의 Documents를 얻으려면 find() 메서드를 사용한다.
  • find()Cursor 인스턴스를 반환하고 이를 사용하면 Iteration을 이용할 수 있다.


  • 예를 들어 다음과 같이 posts Collection의 모든 Documents를 반복할 수 있다.


for post in posts.find():
    pprint.pprint(post)

# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'mongodb', u'python', u'pymongo'],
#  u'text': u'My first blog post!'}
# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'bulk', u'insert'],
#  u'text': u'Another post!'}
# {u'_id': ObjectId('...'),
#  u'author': u'Eliot',
#  u'date': datetime.datetime(...),
#  u'text': u'and pretty easy too!',
#  u'title': u'MongoDB is fun'}


  • find_one()과 마찬가지로 Document를 find()에 전달하여 반환된 결과를 제한할 수 있다.
  • 예를 들어 author"Mike"인 Documents만 가져올 수 있다.


for post in posts.find({"author": "Mike"}):
    pprint.pprint(post)

# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'mongodb', u'python', u'pymongo'],
#  u'text': u'My first blog post!'}
# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'bulk', u'insert'],
#  u'text': u'Another post!'}


12. Counting

  • 얼마나 많은 Documents가 쿼리와 일치하는지 알고 실다면 전체 쿼리 대신 count_documents() 작업을 수행할 수 있다.
  • Collection의 모든 Documents 수를 확인할 수 있다.


print(posts.count_documents({})

# 3


  • 또는 특정 쿼리와 일치하는 Documents 수를 확인할 수 있다.


print(posts.count_documents({"author": "Mike"})

# 2


13. Range Queries

  • MongoDB는 다양한 타입의 고급 쿼리를 지원한다.


  • 예를 들어 특정 날짜보다 오래된 게시물로 결과를 제한하고, author를 기준으로 결과를 정렬하는 쿼리를 수행할 수 있다.


d = datetime.datetime(2009, 11, 12, 12)

for post in posts.find({"date": {"$lt": d}}).sort("author"):
    pprint.pprint(post)

# {u'_id': ObjectId('...'),
#  u'author': u'Eliot',
#  u'date': datetime.datetime(...),
#  u'text': u'and pretty easy too!',
#  u'title': u'MongoDB is fun'}
# {u'_id': ObjectId('...'),
#  u'author': u'Mike',
#  u'date': datetime.datetime(...),
#  u'tags': [u'bulk', u'insert'],
#  u'text': u'Another post!'}


14. Indexing

  • 인덱스를 추가하면 특정 쿼리를 가속화하는 데 도움이 될 수 있으며, Documents를 쿼리하고 저장하는 기능을 추가할 수 있다.
  • 예를 들어 특정 키에 대한 값이 이미 인덱스에 존재하는 경우 추가적으로 삽입되는 Documents를 방지할 수 있다.
  • 먼저 인덱스를 생성한다.


result = db.profiles.create_index([("user_id", pymongo.ASCENDING)], unique=True)

print(sorted(list(db.profiles.index_information()))

# [u'_id_', u'user_id_1']


  • 이제 두 개의 인덱스가 생성되었다.
  • 하나는 MongoDB가 자동으로 생성하는 _id에 대한 인덱스이고, 다른 하나는 방금 생성한 user_id에 대한 인덱스이다.


  • 이제 일부 사용자 프로필을 설정해 보자.


user_profiles = [{"user_id": 211, "name": "Luke"},
                 {"user_id": 212, "name": "Ziltoid"}]

result = db.profiles.insert_many(user_profiles)


  • 인덱스는 user_id가 이미 Collection에 있는 Document를 삽입하는 것을 방지한다.


new_profile = {"user_id": 123, "name": "Drew"}
duplicate_profile = {"user_id": 212, "name": "Tommy"}

result = db.profiles.insert_one(new_profile)
result = db.profiles.insert_one(duplicate_profile)

# Traceback (most recent call last):
# DuplicateKeyError: E11000 duplicate key error index: test_database.profiles.$user_id_1 dup key: { : 212 }

References