Earlier quoted context omitted.
If they're dissecting it at that close a level, it's probably not a company you'd want to work for anyway. At least how I judge take home tests (for a simple backend CRUD app spec), I'm definitely not disqualifying based on surface level details like that. I don't even bother running it on my machine most of the time. What I'm looking for that it doesn't have obvious SQL injections (I've seen this a depressing number…
There are plenty of things in what you wrote. For example, My assignment was to build a command line app. The spec given said nothing about how bad inputs should be handled. How should they be handled? No Op? Throw exception? Friendly error messages? How? Note how the evaluation changes from one person to other, you could be someone who expects an exception to be printed, I could be someone who expects an exception t…
No, not in 24 hours. In about 2 hours. The laundry list of things - api validation, error handling, db migrations, good naming - it's pretty routine. A person writing production apis will have no trouble integrating all of it in a very short period of time. This is after all what we do everyday. If a candidate thinks not having sql injection in the code is an explicit requirement or is going to take a lot of time, then that is not the person for the job.
Here is a pretty simple flask implementation of your laundry list - api validation, db migration, no sql injections, no n+1 queries, good naming, swagger ui...
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_apispec import use_kwargs, marshal_with, MethodResource, FlaskApiSpec
from marshmallow import Schema, fields, validate, ValidationError
from sqlalchemy.orm import joinedload
# setup
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///dev.db'
db = SQLAlchemy(app)
migrate = Migrate(app, db)
docs = FlaskApiSpec(app)
# Return validation errors as JSON
@app.errorhandler(422)
@app.errorhandler(400)
def handle_error(err):
headers = err.data.get("headers", None)
messages = err.data.get("messages", ["Invalid request."])
if headers:
return jsonify({"errors": messages}), err.code, headers
else:
return jsonify({"errors": messages}), err.code
# models
POST_TITLE_LENGTH_MAX = 80
POST_CONTENT_LENGTH_MAX = 5000
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(POST_TITLE_LENGTH_MAX), nullable=False)
content = db.Column(db.String(POST_CONTENT_LENGTH_MAX), nullable=False)
comments = db.relationship('Comment', back_populates='post')
COMMENTER_LENGTH_MAX = POST_TITLE_LENGTH_MAX
COMMENT_LENGTH_MAX = POST_CONTENT_LENGTH_MAX
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
commenter = db.Column(db.String(COMMENTER_LENGTH_MAX), nullable=False)
comment = db.Column(db.String(COMMENT_LENGTH_MAX), nullable=False)
post_id = db.Column(db.ForeignKey('post.id'))
post = db.relationship('Post', uselist=False, back_populates='comments')
# schemas
class CommentSchema(Schema):
id = fields.Int(required=True, dump_only=True)
commenter = fields.Str(
required=True, validate=validate.Length(max=COMMENTER_LENGTH_MAX))
comment = fields.Str(
required=True, validate=validate.Length(max=COMMENT_LENGTH_MAX))
class PostSchema(Schema):
id = fields.Int(required=True, dump_only=True)
title = fields.Str(required=True, validate=validate.Length(
max=POST_TITLE_LENGTH_MAX))
content = fields.Str(required=True, validate=validate.Length(
max=POST_CONTENT_LENGTH_MAX))
comments = fields.Nested(CommentSchema, many=True, dump_only=True)
# dal
def get_post_list():
return Post.query.all()
def get_post(post_id):
return Post.query.options(joinedload(Post.comments)).get(post_id)
def create_post(title, content):
post = Post(title=title, content=content)
db.session.add(post)
db.session.commit()
return post
def add_comment_to_post(post_id, commenter, comment):
comment = Comment(commenter=commenter, comment=comment, post_id=post_id)
db.session.add(comment)
db.session.commit()
return comment
# views
class PostListResource(MethodResource):
@marshal_with(PostSchema(many=True))
def get(self):
return get_post_list()
@marshal_with(PostSchema)
@use_kwargs(PostSchema)
def post(self, title, content):
return create_post(title, content)
class PostResource(MethodResource):
@marshal_with(PostSchema)
def get(self, post_id):
return get_post(post_id)
class PostCommentResource(MethodResource):
@marshal_with(CommentSchema)
@use_kwargs(CommentSchema)
def post(self, post_id, commenter, comment):
return add_comment_to_post(post_id, commenter, comment)
app.add_url_rule('/posts', view_func=PostListResource.as_view('post_list'))
docs.register(PostListResource, endpoint='post_list')
app.add_url_rule('/posts/',
view_func=PostResource.as_view('post'))
docs.register(PostResource, endpoint='post')
app.add_url_rule('/posts//comments',
view_func=PostCommentResource.as_view('post_comment'))
docs.register(PostCommentResource, endpoint='post_comment')