Skip to content

Instantly share code, notes, and snippets.

@kevinpostal
Last active July 25, 2019 23:11
Show Gist options
  • Save kevinpostal/db8b2fbc95c828c94f3cd94e34d81d6e to your computer and use it in GitHub Desktop.
Save kevinpostal/db8b2fbc95c828c94f3cd94e34d81d6e to your computer and use it in GitHub Desktop.
Telesign
from flask import Flask, request, g, jsonify
from flask_restful import Resource, Api
from util import parse_json_body
import sqlite3
import os
# Flask
app = Flask(__name__)
app.config.update(
dict(DATABASE=os.path.join(app.root_path, 'database.db'), DEBUG=True))
api = Api(app)
# SQL RELATED
def connect_db():
rv = sqlite3.connect(app.config['DATABASE'])
rv.row_factory = sqlite3.Row
return rv
def get_db():
if not hasattr(g, 'sqlite_db'):
g.sqlite_db = connect_db()
return g.sqlite_db
@app.teardown_appcontext
def close_db(error):
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()
# END SQL RELATED
# API
class ApiResource(Resource):
def query_db(self, query, args=(), one=False):
cur = get_db().execute(query, args)
rv = cur.fetchall()
return (rv[0] if rv else None) if one else rv
class PalindromeCountView(ApiResource):
def get(self):
result = self.query_db(
"select count(*) from words where is_palindrome is 1", one=True)
return jsonify(result[0])
class AnagramsCountView(ApiResource):
def get(self):
result = self.query_db(
'SELECT count(*) FROM words GROUP BY alphagram HAVING count(alphagram) > 1',
)
return jsonify(len(result))
class AnagramsSingleView(ApiResource):
def get(self, word):
alphagram = "".join(sorted(list(word)))
results = self.query_db('SELECT word FROM words WHERE alphagram = ?',
[alphagram])
return jsonify(
[item['word'] for item in results if item['word'] != word])
class WordsCountView(ApiResource):
def get(self):
result = self.query_db("SELECT count(*) FROM words", one=True)
return jsonify(result[0])
class WordsDeleteView(ApiResource):
def delete(self, word):
cur = get_db()
cur.execute('DELETE FROM words WHERE word = "%s"' % word)
cur.commit()
return
class WordsView(ApiResource):
def __db_insert(self, fields=(), values=()):
cur = get_db()
query = 'INSERT OR IGNORE INTO words (%s) VALUES (%s)' % (
', '.join(fields), ', '.join(['?'] * len(values)))
cur.execute(query, values)
cur.commit()
return
def post(self):
post_body = request.get_json()
for item in parse_json_body(list_of_words=post_body):
self.__db_insert(("word", "alphagram", "is_palindrome"),
(item.get("text"), item.get("alphagram"),
item.get("is_palindrome")))
def delete(self):
cur = get_db()
cur.execute("Delete From words")
cur.commit()
# End API
api.add_resource(WordsView, '/words')
api.add_resource(WordsCountView, '/words/count')
api.add_resource(WordsDeleteView, '/words/<string:word>')
api.add_resource(PalindromeCountView, '/palindromes/count')
api.add_resource(AnagramsCountView, '/anagrams/count')
api.add_resource(AnagramsSingleView, '/anagrams/<string:word>')
if __name__ == '__main__':
app.run(debug=True)
Telesign Take Home Task
=======================
Warm up
-------
Define a function to check if two words are a [palindrome](https://en.wikipedia.org/wiki/Palindrome).
Define a function to check if two words are an [anagram](https://en.wikipedia.org/wiki/Anagram).
The Task
--------
Build a RESTful web service API that allows clients to perform queries against a body of text - such as
[Alice in Wonderland](http://www.gutenberg.org/files/11/11-0.txt). TIP: your solution may be tested against an excerpt
or the full text.
You should design the service to support many HTTP clients. This will be a popular service, therefore performance,
availability and scalability are important considerations in your approach and overall design.
Your RESTful web service should accept JSON content as the request body. It should also return JSON content as responses where
applicable. You should also return appropriate HTTP status codes according to RESTful best practices.
IMPORTANT: The body of text can change through the addition and removal of arbitrary words, changes must be persisted.
You may use any available libraries, OSS and/or systems to support your service.
Please provide documentation that is useful for clients and developers maintaining the solution. This should include
design considerations, tradeoffs and justifications. Identify any weaknesses in your implementation and/or future
enhancements that could be made.
For this exercise, you may ignore punctuation, abbreviation and categorization of real words vs nouns,
pronouns, verbs etc. You should also only consider single whole words when evaluating anagrams or palindromes - no
sentences.
You must support the following RESTful operations:
POST /words
example POST body: ["cat", "act", "bat", "eye"]
accept a JSON array of arbitrary words and add them to the existing data store, if a word already exists it is a no op
GET /words/count
return a count of total unique words in the data store
GET /palindromes/count
return a count of total palindromes in the data store
GET /anagrams/count
return a count of total anagrams in the data store (words with at least one other anagram)
GET /anagrams/<word>
return an array of words that are anagrams of the word queried
DELETE /words/<word>
delete the specified word from the data store
DELETE /words
delete all the words in the data store
Solution
--------
Please provide a link to a private Github or Bitbucket repository with your solution or provide it in a zip file.
@kevinpostal
Copy link
Author

To Run

  • Run the Following Commands / Steps
  1. make init
  2. make run
    (in a new terminal/window...)
  3. make importalice
  4. vist local flask server http://127.0.0.1:5000
  5. access any of the following methods with expected behavior:
    POST /words
    GET /words/count
    GET /palindromes/count
    GET /anagrams/count
    GET /anagrams/
    DELETE /words/
    DELETE /words

Design considerations

  1. Sqlite for the datastore/database.
    • I went with this decision mainly for ease of use.
  2. Flask + Flask-restful handles building a performant api web server.
  3. Word Tokenizer - Need a library for sanitizing/tokenizing an array of words
    • lowercase all the words
    • only match whole words using a regular expression
  4. Use Makefile for building / testing the project
    • Have a cool template for handling Make on MacOS + bash
  5. This Readme

Suggestions

  1. Use Mysql / Postgres - SQLite is not the fastest
    • spend time on building index tables
  2. Use an async model with POST /words
    • Create celery or RQ task for parsing the words so you don't block on request.
  3. Better Tokenization - Use NLTK and improve the way we find valid words.
    - Perhaps only match up nouns.
  4. Docker?
  5. Unit tests / Coverage.
  6. Better Docs
  7. Better Code Commenting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment