Comment

Comment by irahul

> In 24 hours?

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/<int:post_id>',
                    view_func=PostResource.as_view('post'))
    docs.register(PostResource, endpoint='post')

    app.add_url_rule('/posts/<int:post_id>/comments',
                    view_func=PostCommentResource.as_view('post_comment'))
    docs.register(PostCommentResource, endpoint='post_comment')
parent irahul 6 years ago

Discussion

Live from the official Hacker News API

A modern reader for Hacker News · SvelteKit + Skeleton