1

I am new to Python, and I'm starting to learn the basics of the code structure. I've got a basic app that I'm working on up on my Github.

For my simple app, I'm create a basic "Evernote-like" service which allows the user to create and edit a list of notes. In the early design, I have a Note object and a Notepad object, which is effectively a list of notes. Presently, I have the following file structure:

Notes.py
| 
|------ Notepad (class)
|------ Note (class)

From my current understanding and implementation, this translates into the "Notes" module having a Notepad class and Note class, so when I do an import, I'm saying "from Notes import Notepad / from Notes import Note".

Is this the right approach? I feel, out of Java habit, that I should have a folder for Notes and the two classes as individual files.

My goal here is to understand what the best practice is.

Mike Müller
86.1k21 gold badges174 silver badges165 bronze badges
asked May 28, 2013 at 1:44
3
  • Unlike java, you can add whatever you want to a file top level: classes, functions, variables... No worries. Commented May 28, 2013 at 1:46
  • It is personal preference, python allows for whatever structure you want. You could even include your Notepad class AND your Note class both in your notes.py file!!! you could look at open source python projects to get an idea for how some large projecxts are strucxtured Commented May 28, 2013 at 1:48
  • I would just try to get the design first, and then see what makes most sense. For example, putting all the "data layer" classes in one package (separate files or not is not relevant) and the "presentation layer" in another package, and try to see if I can abstract any cross cutting concerns in a "base" class/package Commented May 28, 2013 at 1:50

2 Answers 2

4

As long as the classes are rather small put them into one file. You can still move them later, if necessary. Actually, it is rather common for larger projects to have a rather deep hierarchy but expose a more flat one to the user. So if you move things later but would like still have notes.Note even though the class Note moved deeper, it would be simple to just import note.path.to.module.Note into notes and the user can get it from there. You don't have to do that but you can. So even if you change your mind later but would like to keep the API, no problem.

answered May 28, 2013 at 1:59
Sign up to request clarification or add additional context in comments.

Comments

1

I've been working in a similar application myself. I can't say this is the best possible approach, but it served me well. The classes are meant to interact with the database (context) when the user makes a request (http request, this is a webapp).

# -*- coding: utf-8 -*-
import json
import datetime
class Note ():
 """A note. This class is part of the data model and is instantiated every
 time there access to the database"""
 def __init__(self, noteid = 0, note = "", date = datetime.datetime.now(), context = None):
 self.id = noteid
 self.note = note
 self.date = date
 self.ctx = context #context holds the db connection and some globals
 def get(self):
 """Get the current object from the database. This function needs the
 instance to have an id"""
 if id == 0:
 raise self.ctx.ApplicationError(404, ("No note with id 0 exists"))
 cursor = self.ctx.db.conn.cursor()
 cursor.execute("select note, date from %s.notes where id=%s" % 
 (self.ctx.db.DB_NAME, str(self.id)))
 data = cursor.fetchone()
 if not data:
 raise self.ctx.ApplicationError(404, ("No note with id " 
 + self.id + " was found"))
 self.note = data[0]
 self.date = data[1]
 return self
 def insert(self, user):
 """This function inserts the object to the database. It can be an empty
 note. User must be authenticated to add notes (authentication handled
 elsewhere)"""
 cursor = self.ctx.db.conn.cursor()
 query = ("insert into %s.notes (note, owner) values ('%s', '%s')" % 
 (self.ctx.db.DB_NAME, str(self.note), str(user['id'])))
 cursor.execute(query)
 return self
 def put(self):
 """Modify the current note in the database"""
 cursor = self.ctx.db.conn.cursor()
 query = ("update %s.notes set note = '%s' where id = %s" % 
 (self.ctx.db.DB_NAME, str(self.note), str(self.id)))
 cursor.execute(query)
 return self
 def delete(self):
 """Delete the current note, by id"""
 if self.id == 0:
 raise self.ctx.ApplicationError(404, "No note with id 0 exists")
 cursor = self.ctx.db.conn.cursor()
 query = ("delete from %s.notes where id = %s" % 
 (self.ctx.db.DB_NAME, str(self.id)))
 cursor.execute(query)
 def toJson(self):
 """Returns a json string of the note object's data attributes"""
 return json.dumps(self.toDict())
 def toDict(self):
 """Returns a dict of the note object's data attributes"""
 return {
 "id" : self.id,
 "note" : self.note,
 "date" : self.date.strftime("%Y-%m-%d %H:%M:%S")
 }
class NotesCollection():
 """This class handles the notes as a collection"""
 collection = []
 def get(self, user, context):
 """Populate the collection object and return it"""
 cursor = context.db.conn.cursor()
 cursor.execute("select id, note, date from %s.notes where owner=%s" % 
 (context.db.DB_NAME, str(user["id"])))
 note = cursor.fetchone()
 while note:
 self.collection.append(Note(note[0], note[1],note[2]))
 note = cursor.fetchone()
 return self
 def toJson(self):
 """Return a json string of the current collection"""
 return json.dumps([note.toDict() for note in self.collection])

I personally use python as a "get it done" language, and don't bother myself with details. This shows in the code above. However one piece of advice: There are no private variables nor methods in python, so don't bother trying to create them. Make your life easier, code fast, get it done

Usage example:

class NotesCollection(BaseHandler):
 @tornado.web.authenticated
 def get(self):
 """Retrieve all notes from the current user and return a json object"""
 allNotes = Note.NotesCollection().get(self.get_current_user(), settings["context"])
 json = allNotes.toJson()
 self.write(json)
 @protected
 @tornado.web.authenticated
 def post(self):
 """Handles all post requests to /notes"""
 requestType = self.get_argument("type", "POST")
 ctx = settings["context"]
 if requestType == "POST":
 Note.Note(note = self.get_argument("note", ""), 
 context = ctx).insert(self.get_current_user())
 elif requestType == "DELETE":
 Note.Note(id = self.get_argument("id"), context = ctx).delete()
 elif requestType == "PUT":
 Note.Note(id = self.get_argument("id"),
 note = self.get_argument("note"),
 context = ctx).put()
 else:
 raise ApplicationError(405, "Method not allowed")

By using decorators I'm getting user authentication and error handling out of the main code. This makes it clearer and easier to mantain.

answered May 28, 2013 at 1:57

2 Comments

Thanks! This is awesome; your example is very similar to mine in concept, and I'll likely look to this tomorrow when I start implementing the SQLite persistence. Much appreciated!
Since you appreciated my answer, I spent some time improving this code and adding some "docstrings". I hope you find this useful :)

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.