top | item 22264245

(no title)

irahul | 6 years ago

> 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')

discuss

order

kamaal|6 years ago

Exactly, if you provide the laundry list its easy to validate your assignment against it.

You submitted code which doesn't have things in my laundry list. Noticeably your db calls aren't surrounded by try/except. You have written no documentation. You didn't ship the code with git repo. No unit or functional test cases. I also expect these days one would use Python's typing library. Most of this helps in code maintainability. The fact that your assignment has none of this means you can't be hired to write production code ....

... See where this is going?

Despite you having written the code, someone with a different laundry list of quality checks can fail you.

irahul|6 years ago

> Exactly, if you provide the laundry list its easy to validate your assignment against it.

There is no laundry list. There is an expectation that a senior engineer writes codes like a senior engineer.

> You submitted code which doesn't have things in my laundry list. Noticeably your db calls aren't surrounded by try/except. You have written no documentation. You didn't ship the code with git repo. No unit or functional test cases. I also expect these days one would use Python's typing library. Most of this helps in code maintainability. The fact that your assignment has none of this means you can't be hired to write production code ....

Not sure if you are really this dense or playing dense - the point of the comment was to show that your assumption about api validation and sql injection being explicit requirements or taking too much time is ridiculous. It' simple that it can be written in a comment in about 10 minutes, not the "24 hours" or whatever you claim it is going to take.

The very fact that you think sql injection is an explicit requirement or takes work will be an instant deal breaker for any position with the possible exception of fresh grad positions.

> ... See where this is going?

Yes, I do. You are arguing reductio ad absurdum to hide the fact that the things which you claimed unreasonable are in fact routine, and your time estimates are off by order of 10.