Only this pageAll pages
Powered by GitBook
1 of 71

Ask Documentation

Loading...

Getting Started

Loading...

Loading...

Routes & Requests

Loading...

Loading...

Loading...

Loading...

Loading...

Response

Loading...

Loading...

Classes

Loading...

Loading...

Data Types

Loading...

Built-in Utilities

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Database

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

JWT Authentication

Loading...

Loading...

Loading...

Loading...

Loading...

Decorators

Loading...

Loading...

Loading...

Security

Loading...

Loading...

Loading...

Configuring the Transpiler

Loading...

Modules & Libraries

Loading...

Loading...

Loading...

Development Tools

Loading...

Loading...

Loading...

Loading...

Loading...

Contribute

Loading...

Loading...

Loading...

Install & Get Started

How to install Ask.

Setup a Development Environment for Ask.

We recommend that you always create a Python virtual environment for your Ask projects. This way you can always use the correct version of Ask for every project. If you use it globally and then upgrade Ask, previous features might break. You can learn more about Ask's versioning system here, and more about virtual environments here.

  • Create a new folder.

  • Create a new Python virtual environment:

    • $ python3 -m venv venv

  • Activate it:

    • $ source venv/bin/activate

  • Install Ask.

    • $ pip install ask-lang

  • Create a .ask file to hold your app.

  • Run it with:

    • $ ask [your app].ask

We recommend that you use pipx to install Ask if you want to use it globally, but you can also use pip to intall it without a virtual environment.

Hello, World!

Beginners guide to Ask

Initial Project Setup

  • Create a new folder.

  • Create a new Python virtual environment:

    • $ python3 -m venv venv

  • Activate it:

    • $ source venv/bin/activate

  • Install Ask.

    • $ pip install ask-lang

Hello, World! In Ask.

  • Create a file called hello_world.ask.

  • Open it in your favourite text editor or IDE, and set the syntax language to Python.

  • Then write the following in the file.

Run the App

  • Open your terminal in the project folder.

  • Make sure that the virtual environment is activated and Ask is installed ($ ask -v).

  • Run your app with:

    • $ ask hello_world.ask

  • This will start a local server on port 5000.

  • Open a browser and navigate to:

    • http://127.0.0.1:5000/hello

    • You should see the message "Hello, World!".

Congratulations! You have now created your first Ask app. You can now move on to the next section of this documentation.

Please let us know if you're facing any problems. We're happy to help!

Defining Routes

How to define routes.

Parameters are defined with angle brackets (< & >).

You define routes withe the @ character followed by an (e.g. GET, POST, PUT, etc.). Routes work like functions.

@get('/hello'):
    respond('Hello, World!')
# A basic GET route.
@get(’/api/v1/route_1’):
    # code goes here...

# A route with query parameters.
@post(’/api/v1/route_2/<param_1>’):
    print(param_1)
HTTP method

CORS

Configure CORS

Cors is built in by default in all Ask apps. You can also configure CORS to e.g. only allow specific origins or only enable CORS on specific routes.

Configuring CORS

CORS can be configured in your projects Askfile. Configuration options are added in the [cors] section.

The configuration options supported are the same as Flask-Cors supports. Flask-Cors documentation: https://flask-cors.corydolphin.com/en/latest/api.html

Example

Askfile.toml
[cors]
origins = ['https://example.com', 'http://localhost:8080']
methods = ['GET', 'PUT']
resources = [r'/api/*']

Routes

Routes/Endpoints.

Routes are the main part of an Ask app. Routes are functions are executed when a request is made to them. All routes are built on these three actions:

  • Take in Data.

    • E.g. JSON or Form Data.

  • Action/Do Something.

    • E.g. data processing, database communication, etc.

  • Response.

    • Send back a JSON response (optionally with status codes).

Request Data

How to read and handle request data.

There are a few built-in variables and functions accessible in routes, for reading, verifying and working with request data.

Get Request Data

Helpful Functions For Working With Request Data

require_keys()

Not specifically for requests, this function can be used in other circumstances.

Takes in two parameters: a list of keys to check for, and a dictionary (e.g. body).

Get the JSON body of a request.

  • Returns a dictionary.

Get Form Data

  • Returns a dictionary.

Get Query Parameters

  • Returns a dictionary.

Get Files

  • Returns a dictionary.

All Request Data in One Dictionary

  • Returns a dictionary.

  • Combines body, form, and args into one dictionary.

# Verify that the request's JSON body contains all required parameters.
@post('/note'):
    if require_keys(['title', 'text'], body):
        # Not all required keys where found
        status('Please provide all required parameters.', 400)
    
    # All keys where found
    print('Sucess!')
    # ...

Protecting Routes

You can protect any route and require a valid token when accessing that route with the &protected decorator. You can access the route if you pass in a valid token as a query parameter called token with your request. The server will otherwise respond with 400/401 and "Missing token!" or "Invalid token".

The server will also respond with "Invalid token" if the token is expired.

&protected
@post('/note'):
    respond('Only viewable with a valid token.")

@get('/public'):
    respond('This is viewable without a token.")

HTTP Status Codes & Methods

Cheatsheet with HTTP status codes and HTTP methods.

Status Codes

Informational (1XX)

  • 100 Continue.

  • 101 Switching Protocols.

  • 102 Processing.

  • 103 Early Hints.

Success (2XX)

  • 200 OK

  • 201 Created

  • 202 Accepted

  • 203 Non-Authoritative Information

  • 204 No Content

  • 205 Reset Content

  • 206 Partial Content

  • 207 Multi-Status

  • 208 Already Reported

  • 226 IM Used

Redirection (3XX)

  • 300 Multiple Choices

  • 301 Moved Permanently

  • 302 Found

  • 303 See Other

  • 304 Not Modified

  • 305 Use Proxy

  • 306 Switch Proxy

  • 307 Temporary Redirect

  • 308 Permanent Redirect

Client Errors (4XX)

  • 400 Bad Request

  • 401 Unauthorized

  • 402 Payment Required

  • 403 Forbidden

  • 404 Not Found

  • 405 Method Not Allowed

  • 406 Not Acceptable

  • 407 Proxy Authentication Required

  • 408 Request Timeout

  • 409 Conflict

  • 410 Gone

  • 411 Length Required

  • 412 Precondition Failed

  • 413 Payload Too Large

  • 414 URI Too Long

  • 415 Unsupported Media Type

  • 416 Range Not Satisfiable

  • 417 Expectation Failed

  • 418 I'm a teapot

  • 421 Misdirected Request

  • 422 Unprocessable Entity

  • 423 Locked

  • 424 Failed Dependency

  • 425 Too Early

  • 426 Upgrade Required

  • 428 Precondition Required

  • 429 Too Many Requests

  • 431 Request Header Fields Too Large

  • 451 Unavailable For Legal Reason

Server Errors (5XX)

  • 500 Internal Server Error

  • 501 Not Implemented

  • 502 Bad Gateway

  • 503 Service Unavailable

  • 504 Gateway Timeout

  • 505 HTTP Version Not Supported

  • 506 Variant Also Negotiates

  • 507 Insufficient Storage

  • 508 Loop Detected

  • 510 Not Extended

  • 511 Network Authentication Required

HTTP Methods

  • GET

  • HEAD

  • POST

  • PUT

  • DELETE

  • CONNECT

  • OPTIONS

  • TRACE

  • PATCH

Class Instance Variable

The name of the variable that points to the class instance.

The variable name is: self.

The same thing as this in javascript.

Ask and Databases

How Ask handles database work.

Databases are easy to set up and easy to work with. Ask's design really shines when it comes to databases. Everything should be as simple and straightforward as possible. Ask reduces the needed boilerplate to get started to basically 0 lines, when compared to Flask.

Practical

  • All functions, variables, etc. used when working with databases are called on the built-in global db variable.

Technical

  • Ask uses SQLAlchemy behind the scenes. This can be useful to know when e.g. looking up errors.

  • The default database for all Ask applications is SQLite, it is however possible to connect other databases, like e.g. MySQL.

  • The database is by default stored in a file called db.db in your project's source directory.

CLI Flags

  • -v / --version: Shows what version of Ask is installed.

  • -d / --dev: Activates development mode.

  • -xd --extra-dev: Activates extra development mode.

  • -h / --help: Shows a help menu.

  • --module-transpile: A special mode used when transpiling modules. This is not intended to be user-ran.

  • --include-transpile: Similar to --module-transpile but used when transpiling include imported modules.

Contribute Code

Are you interested in contributing to our awesome FOSS project? Please visit Ask's GitHub page for more information.

Initialization/Constructor Method

The initialization/constructor method for classes and database models is: init.

Example

Serialization

You might also need a serialization method in the model for getting all values of the table in the format of ex. a dictionary. A serialization method can look like this:

Example

Initialization/Constructor

The initialization/constructor method is used when a new instance of the model is created. This function can set default values, as well as initial values for the columns.

The name of the initialization methods needs to either init or __init__. It has to at least take in self as it's first parameter.

Example

What are Decorators?

What is a decorator.

Decorators are a way of extending the functionality of functions by wrapping them in another function. Decorators in Ask work in the exact same way that decorators do in Python. The only difference is that decorators are used with the & symbol

Decorators are added on the lines before a function definition like so:

Making Requests to Protected Routes

How to send a JWT token to protected routes.

Columns

Columns are defined at the top of the model.

Columns are described by:

  • a data type.

  • (optional) a maximum length of the data (see line 3 in the example).

  • (optional) additional attributes.

Column Data Types

  • int: Integer (whole number, ex. 35).

  • str: String (a sequence of characters).

  • float: Floating point number (ex. 3.5).

  • bool: Boolean value (True or False).

  • bytes: Bytes array.

  • datetime: Datetime type (Python datetime object).

  • list_id: An alias for int, can be used to improve readability when working with database lists.

Other Attributes

  • pk : Primary key.

  • unique: Unique.

  • nullable: Nullable.

  • basic_ignore: Ignore the column in the basic decorator code.

Example

Add

Adding a row.

Adding a row to a table is a two-step process.

Steps

1. Creating a new instance of a database model

2. Passing that instance into the db.add() method.

Example

Importing Python Modules

You can import any* python module and/or package into your Ask app. This is because Ask transpiles to Python. You can both import local .py files or modules & packages from the PyPI.

* = As long as it's name doesn't conflict with any built-in Ask functions/classes, you have it installed (if it's from the PyPI), and it's compatible with your Python version.

How To

You can import Python modules in the exact same way as you would in Python.

You can then access the module's/package's functions and other properties with: [module/package name].[function/property/class/etc.]

Delete

Deleting rows

1. Select

2. Delete

Delete the row.

It's good practice to call the serialization method s(), that way you can also utilize the built-in function.

Essentially decorators are functions that take another function as their parameter. There are a few built-in decorators like and . The following chapters will focus on how to create and use your own custom decorators.

You can pass the JWT token along with your request as a query parameter called token there's some more information available in chapter.

First, select a row and store it in a variable. Read more about selecting .

class MyClass:
    def init(self, param1, param2):
        self.param1 = param1
        self.param2 = param2


my_class = MyClass('abc', 123)
db_model MyModel:
    id = db.col(db.int, db.pk)
    column1 = db.col(db.str(100))
    column2 = db.col(db.float, db.unique, db.nullable)
    
    def _init(self, column1, column2):
        self.column1 = column1,
        self.column2 = column2

    def s(self):
        return {
            id: self.id,
            column1: self.column1,
            column2: self.column2
        }
db_model MyModel:
    id = db.col(_db.int, db.pk)
    column1 = db.col(db.str(100))
    column2 = db.col(db.float, db.unique, db.nullable)
    
    def _init(self, column1, column2):
        self.column1 = column1,
        self.column2 = column2
    
    ...
&decorator1
&another_decorator
def my_function():
    ...
db_model MyModel:
    id = db.col(db.int, db.pk)
    column1 = db.col(db.str(100))
    column2 = db.col(db.float, db.unique, db.nullable)

    ...
db_model Product:
    id = db.col(db.int, db.pk)
    name = db.col(db.str(100), db.unique)
    description = db.col(db.str(200))
    price = db.col(db.float)
    qty = db.col(db.int)
    
    ...
new_instance = MyModel(value1, value2, ...)
db.add(new_instance)
&basic
db_model Note:
    id = db.col(db.int, db.pk)
    title = db.col(db.str)
    text = db.col(db.str)


@post('/note'):
    if require_keys(['title', 'text'], body):
        status('Please provide all required parameters', 400)
    
    # Creates a new instance of "Note"
    new_note = Note(boy['title'], body['text'])
    db.add(new_note)
    
    respond(new_note.s())
import module
from package import module
import module as name
from package import *

# etc.
my_row = MyModel.db.get(5)
...

db.delete(my_row)
serialize()
&basic
&limit
this
here

Deep()

Find a dictionary in a list of dictionaries by value.

Deep() returns a dictionary from a list of dictionaries by value. This can be useful when you need to pick out e.g. a product by id from a list of products represented by dictionaries.

Usage

deep(..., search requirements)

Parameters:

  • A list.

    • That contains only ditionaries.

  • A dictionary

    • With the key & value to search for.

Example

products = [
    {
      id: 1,
      title: 'Product 1'
    },
    {
      id: 2,
      title: 'Product 2'
    },
    {
      id: 3,
      title: 'Product 3'
    }
]

# Get the product with the id of 2.
selection = deep(products, {id: 2})

Pattern Matching

Regular Expressions & Abnormal Expressions

You can use pattern/text matching in two different ways. Ask also supports pythons raw strings, which is the prefered way to write patterns.

Using Regex

Use Python's own built-in re library for regexes.

Using Abnex

Abnex or Abnormal Expressions is an alternative to regular expressions. It's goal is to make it easier to read and write patterns. Not only does it allow for e.g. spaces inside the pattern and less forward slashes, but it also uses characters that might be considered to be more "logical" e.g. not is represented by an exclamation point !, groups with curly brackets { }, and so on.

Read more about Abnex here.

Call Abnex's different functions on the pre-imported ab object.

Require_keys()

Used for validating dictionaries. Used for checking that a dictionary has a set of keys. This is useful when validating the body of a request (body). Returns False if all keys were found and True if one or more keys are missing.

Usage

require_keys(required keys, ...)

Parameters

  • A list.

    • of strings.

  • A dictionary

  • The dictionary to search for the required keys in.

  • E.g. body.

Example

@post('/login'):
    if require_keys(['email', 'password'], body):
        status('Please provide an email address and a password.', 400)
    
    # All keys where found in the request JSON body.
    ...

Models/Classes

Database models/classes

A database model is defined with the db_model keyword (db_class is deprecated) keyword followed by the name of the model.

db_model MyModel:
    ...

Models are built up of three parts (two are required and one is optional).

1. Columns

2. Initialization/Constructor

3. Serialization (optional, but recommended)

JSON Response

How to send a JSON response.

Use the respond() function (or the deprecated respond keyword) to return JSON data. This will also include the HTTP status code 200 OK.

Usage

respond(...)

Parameters:

  • Any datatype than can be JSON serialized:

    • dictionary.

    • list.

    • string.

    • integer.

    • float.

    • boolean.

    • tuple.

Example

@get('/greet/<name>'):
    respond('Hello ' + name)

Introduction

Learn about Ask's built-in authentication system.

Ask has a built-in system for JWT (JSON Web Token) authentication. This system allows you to protect certain routes by requiring the requester to send a valid token along with their request.

The authentication system is pre-configured and included by default in all Ask applications. You don't have to set up anything to get started.

The system is designed to be flexible and easy to get up and running. We are going to use a database for storing user information in these docs, but that's not even needed. It is entirely possible to just make a basic authentication system where no user data is stored in between sessions (though, there's not really any point in doing that).

Technical

We are using PyJWT behind the scenes, which means that Ask's authentication is as secure as the PyJWT library.

Quick_set()

Easily update a dictionary with the values from another.

The quick_set() function updates a dictionary with the values from another dictionary with the same keys.

Usage

quick_set(..., ...)

Parameters

  • A dictionary

  • A dictionary

Example

user = {
    name: 'Name',
    email: '[email protected]
}

# Let's say that the request body looks like this:
request_body = {
    name: 'New name',
    email: '[email protected]'
}

# Update user
user = quick_set(user, request_body)

JSON Response With an HTTP Status Code

Use the status() function to return JSON data with an HTTP status code.

This function is useful when returning errors.

Usage

status(..., status code)

Parameters:

  • The same as respond().

  • Integer.

  • HTTP status code.

Example

@get('/greet'):
    if require_keys(['name', 'age'], body):
        status('Please provide all required parameters.', 400)
    
    respond({
        name: body['name'],
        age: body['age']
    })

Update

Update a row.

1. Select

First, select a row and store it in a variable. Read more about selecting here.

my_row = MyModel.db.get(5)

2. Update

You update a row's columns like this:

...

my_row.column 1 = new value
my_row.column 2 = new value

...

3. Save

You need to save/commit your changes whenever you've updated a row.

...

db.save()

Email

Send email from you Ask app

Ask has a built in tool for sending email messages. You just have to configure it to use an SMTP server (e.g. Gmail) and you're good to go.

Setup/Configuration

You configure the mail tool from your projects Askfile. In this example we're connecting to gmail.

[mail]
debug=true
server='smtp.gmail.com'
port=465
use_tls=false
use_ssl=true
username=''
password=''

When using Gmail, make sure to either use App specific passwords, or enable less secure apps.

You also have to enable IMAP, and sometimes complete the steps here: https://accounts.google.com/b/2/DisplayUnlockCaptcha

The Mail Class

All functions and events related to emails are available under built-in the mail class.

Example App

@get('/email'):
	msg = mail.msg('Subject line', ['[email protected]'], 'Message body.')
	mail.send(msg)

Editor Syntax Highlighting

How to get syntax highlighting for .ask files

Since Ask’s syntax is quite similar to Python, the best way to get syntax highlighting at this point in time since we haven’t built any custom syntax highlighting tools/plugins is to just tell your editor to render .ask files as Python code.

We are definitely planning on building our own solution in the future, but this method works well enough for the time being.

Dictionaries

Dictionaries work in the exact same way as Python dictionaries, except for one difference.

In Ask dictionaries can be used with a JavaScript-like syntax (you don't have to put quotes around keys, you can still put them there if you want to):

Example (Ask)

product = {
    id: 1,
    name: 'Product 1',
    price: 40.5
}

Example (Python)

product = {
    'id': 1,
    'name': 'Product 1',
    'price': 40.5
}

Check if a Row Exists

Check if a row exists in a database table.

db.exists()

Used for checking if a row exists in a table.

Usage

db.exists(...)

Parameters:

  • A query object of a database row, returned by the different selection methods.

Example

...

my_row = MyModel.db.get_by(email='[email protected]').first()

if db.exists(my_row)
    respond(my_row.s())

status('Not found.', 404)

You have to use first() since db.get_by() returns a list* of rows.

* = Not a "normal" list, a query object.

Versioning System

Learn about Ask's versioning system

Ask versions are described by three numbers, e.g. 1.0.0

  • The first number increases if:

    • There are major new features.

    • There's a drop for backwards compatibility.

    • E.g. a built-in function gets renamed.

  • The second number increases if:

    • We add smaller new features.

    • We change existing features but keep backwards compatibility.

    • Bigger bug fixes.

  • The third number increases if:

    • There are small bug fixes and improvements.

    • We change something behind the scenes that doesn't impact the end-user.

You can always safely install updates where only the last two digits increases.

If the second one increases you might also want to read the release notes to learn about possible new features.

If the first number increases we recommend that you always read the release notes as there might be breaking changes!

Create and Use Custom Decorators

How to create and use your own custom decorators.

A decorator uses another concept of programming called first-class functions. Essentially it allows you to treat a function as a variable by passing it to another function.

The structure of a decorator is pretty simple. The decorator is given a name and you can then choose to either do some actions before executing the inner function (the one that gets passed in) or after, or both. It's easier to explain by example

A decorator is defined with the keyword decorator followed by the name of the decorator. After that it works like a normal function except fo one thing. You can call inner() somewhere in the decorator body, this is where the "passed in" function (the one the decorator is used on) will get executed. You can put any code before or after this function, you can also execute inner() multiple times or e.g. in a loop.

Then put & and the name of a decorator to decorate any function.

Example

decorator my_first_decorator:
  print('Before')
  inner()
  print('After')

&my_first_decorator
def my_function(message):
  print(message)

my_function('Hello')

The output of this program would be:

Before
Hello
After

As you can see what happened is that the function my_function got passed into my_first_decorator, which then first printed "Before" then ran the "inner" function, and then printed "After".

Select

Selecting row(s).

There are three ways of selecting rows.

  1. All rows in a table.

  2. Selecting one row in a table, by id/primary key.

  3. Selecting all rows that match a filter.

1. Selecting All / all()

This will select all rows in the table the MyModel model uses.

MyModel.db.all()

2. Selecting One Row by Id/Primary Key / get()

This will select the row in the table that has the id/primary key 5.

MyModel.db.get(5)

The reason we say id/primary key is that usually, you call the column that holds the primary key id but you don't have to, you can call it whatever you want.

3. Selecting All That Match / get_by()

This will select all rows in the table with the same email address in the email column.

MyModel.db.get_by(email='[email protected]')

This will return an array. You can get the first match by calling .first() after the get_by() method.

MyModel.db.get_by(email='[email protected]').first()

db.get_by() returns a list of matches (even if there's only one). Use .first() to get the first match (or only match if there's only one).

Hashing

Hash sensitive information like passwords and email addresses.

Hashing is the practise of converting data into another format to secure or hide it. Hashing is done by hashing algorithms that are mathematically designed to be one-way, meaning it’s hard to decode back to the original format without some sort of key for example.

Ask’s built-in hashing solution uses sha256 encryption. SHA256 turns a given string into a 256 character long string.

Use the hash object for this.

hash()

Returns the given value enypted by the SHA256 algorithm.

Usage

hash.hash([value])

Parameters:

  • Typically a string.

check()

Check if a value corresponds with a hash. Used for e.g. verifying passwords. Returns True/False.

Usage

hash.check([hash], [value])

Parameters:

  • A SHA256 encrypted value.

  • The value to compare.

  • This will e.g. be the user-submitted plain text password your verifying with the one stored in the database that has been hashed.

This function only helps you save a bit of typing. Technically you could just do:

if hash_string == hash.hash(’not hashed’):
    # True
    ...
else:
    # False
    ...

But using .check is a bit easier to both read and write.

Running in development mode

You can activate development mode by running ask with the -d or --dev flag. This will give you some development and debugging related console messages. For example, development mode will show you full error messages without any special formating or parsing.

Running Ask in development mode will also activate development mode for the local Flask testing server.

You can also activate extra development mode. This mode is used when working on Ask itself. Run Ask with the -xd or --extra-dev flag to activate it. This turns off the appending of initial Flask boilerplate code to the output app.py file. This will cause errors in the console.

Route Security

How to secure routes.

Limiting Requests

Ask has a built-in limiter that can limit the number of times an IP address can make a request to a specific route during a period of time. E.g. twice a minute, 100 times an hour, etc.

You can limit a route by adding a decorator before the route definition. The decorator takes in as an argument a string with a sort of natural language syntax. You can either use the word "per" or a slash (/).

# This can only be called from the same IP, once every two minutes.
&limit('1 per 2 minute')
@get('/route1'):
    print('Hello, World!')

# Max 200 request per day from the same IP.
&limit('200/day')
@get('/route2'):
    print('Hello, World!')

Available rules

  • Seconds.

  • Minutes.

  • Hours.

  • Days.

  • Years

Automatic API documentation

By default all Ask apps will be automatically generating a basic API documentation of all the routes in the app, (provided by flask-selfdoc). You can find the documentation by going to /docs. You can also filter out routes that requires authentication by going to /docs/public. You can also only show protected routes by going to /docs/private.

The documentation shows you what path and what query parameters a given route uses. At this time it does not show any information relating to JSON, or any other request data format.

Built-in Decorators

There are a few already defined built-in decorators you can use throughout your app.

&limit

Limit the number of requests the same IP can make to a route. Read more.

&basic

Automatically generate the standard database model structure with an initialization and serialization method. Read more.

Bug Reports

Found a bug in Ask? Please report it ASAP so that other people won't face the same issue. You can either shoot us an email (contact information listed on our website) or (preferably) by making an issue report on GitHub.

Feature Requests

We are always happy to get suggestions for new features or ways that we could improve existing ones. Please visit our main website to get contact information.

Environment Variables

Environment variables or (env variables) are values set outside of your application. They are usually used for configuring things inside your app. You can e.g. use them to securely store your API keys locally and still use public source control (GitHub for example) without exposing them.

Environment variable names are usually in all caps and words are separated with underscores. Example: MY_API_KEY.

Use the built-in object env to work with environment variables.

get()

Returns the value of an environment variable.

Usage

env.get([variable name])

Parameters:

  • String.

  • The name of the environment variable.

body
form
args
files
req

The &basic decorator

Write database models faster by reducing boilerplate with the help of this decorator.

It might feel a bit tedious to have to create the columns, the initialization method, and a serialization method for each new database model. The &basic decorator automatically generates the initialization and serialization methods for you, so you only have to define the columns.

You can use the &basic decorator your model fulfils the following criteria:

  1. The init() method assigns all columns a value that gets passed into the method.

  2. The s() method returns all columns values as separate key-value pairs.

The decorator should be placed on the line before the model definition.

Examples

Where you can use the decorator.

Without using the &basic decorator.

Using the &basic decorator.

You can also leave out the id column if you want.

Where you can't use the decorator.

In this example, the &basic decorator can't be used since the init() method set's a value that doesn't get passed in, and s() returns two of the columns combined into one key-value pair.

Database Lists

Store a list-like object in the database

Storing lists in the context of databases can be challenging. The easiest way to overcome this is to restructure your project to use multiple models and link them together with ids. Though this method might still be a bit complex. So, to solve this problem Ask has built-in database models ready to go for storing a list (list-like) objects.

The built-in database model is called List and it can be used along with the database column data type list_id (which is just an alias for int, but it improves readability) to link it in a table.

Create a DB List

This will automatically also add the row to the database.

Properties of the List Object

CRUD

Create (add), Read (select), Update, Delete.

db.add()

Usage

Parameters:

  • A database model instance.

db.all()

Usage

Parameters:

- None

db.get()

Usage

Parameters:

  • The id/primary key

    • Same datatype as the primary key in the model (usually an integer).

db.get_by()

Usage

Parameters:

  • A series of named parameters.

    • db.get_by(column=value, other_column=other_value)

db.save()

Usage

Parameters:

- None

db.delete()

Usage

Parameters:

  • Query object

Includes

Split your apps into multiple files that you can import/insert into one another.

Includes allows you to move parts of your application into separate files. Includes (like the name suggests) simply inserts code from other files into another. The place where you use the include keyword will be the location to which the code will be inserted to.

Usage

Any .ask file can be treated as an include. You don't need to add anything special into include modules. Simple insert their contents into another .ask file using the include keyword.

Example

main.ask

module_b.ask

(Result) (app.ask)

Include module paths

You can put include modules in the same directory as you're importing them from. You can also put them in sub-folders, and point to their paths using dots (like slashes in normal file paths)

File structure

Pointing to sub-folders

Askfile.toml

Optional configuration options. Askfile.toml

The Askfile or Askfile.toml is a file that lives in the same folder as your app's source code file. The file allows you to configure different parts of your project and the Ask transpiler. Every project can have its own Askfile. The file is also optional, Ask automatically detects it if it's in the correct location.

Syntax

The file is a .toml file and is grouped by sections that contain rules.

Sections

DB

Path

  • String.

The path rule allows you to define a custom file name for the automatically generated SQLite database file. The default is db.db you could set this rule to e.g. '/database/my_db.db'.

Custom

  • Boolean (true/false). Default is false.

This rule allows you to use an entirely different database than the automatically generated SQLite one. If you set this to true then the path rule can be used to point to e.g. a MySQL database.

Rules

Underscores

  • Boolean (true/false). Default is true.

This rule was introduced when Ask stopped enforcing the leading underscore syntax for built-in objects. E.g. previously you had to say: _auth.login() but now it's also possible to just say: auth.login() same goes for all other built-in words where a leading underscore was/is used. This rule allows you to enforce the backwards compatibility to be turned off. Right now it's still allowed to use both styles (even mixing them), so this rule allows you to turn that off.

System

keep_app

  • Boolean.

Default is true. Set this to false if you want the output transpiled code file to be deleted once Ask quits. This is recommended when building non-web/API apps for example.

server

  • Boolean.

Default is true. Set this to false if you don't want Ask to automatically start a web server once the transpilation is done. This is useful if you want to deploy the app in a more customized way.

output_path

  • String.

Kind of the same as the path rule for the database. This rule allows you to customize what the output file should be called and where it should be located. The default is the current working directory and with the file name app.py.

Sorting

How to sort selection query results.

db.sort()

Used for sorting database query results. You can either sort the results in a decending or an ascending order.

Usage

Parameters:

  • Either db.desc or db.asc.

  • Passed into the sorting order function.

  • Database model class column property (see the example).

Example

Importing an Ask Module

You can split up your Ask app into smaller files and then import them into one another. You do this with the extend keyword.

How To

You can then access the module's functions and other properties with: [module name].[function/property/class/etc.]

  • Basically a serialization method.

  • Returns a standard list.

  • Note! Don't mutate the return value of this method, it won't update the list in the database.

Example:

  • Takes in an integer.

  • Returns a single item from the list by index number.

Example:

  • Append to the list.

Example:

  • Remove an element from the list.

  • Takes in an integer.

  • Removes by index number.

Example:

  • Overwrite the list with a new one.

  • Takes in a list.

Example:

Inserts rows into tables. See for more information.

Returns all rows from a table. See for more information.

Selects rows by id/primary key. See for more information.

Selects all rows from a table that matches one or more columns. See for more information.

Saves/commits changes made to a table. You need to save after updating or deleting a row. See for more information.

Deletes rows in tables. See for more information.

db_model MyModel:
    id = db.col(_db.int, db.pk)
    column1 = db.col(db.str(100))
    column2 = db.col(db.float, db.unique, db.nullable)
    
    def _init(self, column1, column2):
        self.column1 = column1,
        self.column2 = column2

    def s(self):
        return {
            id: self.id,
            column1: self.column1,
            column2: self.column2
        }
&basic
db_model MyModel:
    id = db.col(db.int, db.pk)
    column1 = db.col(db.str(100))
    column2 = db.col(_db.float, db.unique, db.nullable)
db_model MyModel:
    id = db.col(db.int, db.pk)
    column1 = db.col(db.str(100))
    column2 = db.col(db.float, db.unique, db.nullable)
    
    def _init(self column2):
        self.column1 = 'Hello, World!',
        self.column2 = column2

    def s(self):
        return {
            id: self.id,
            column1: self.column1 + str(self.column2)
        }
my_list = db.list([1, 2, 3, 4])  # The passed in list is optional.
my_list = db.list(['a', 'b', 'c'])
my_list.list()  # ['a', 'b', 'c']
my_list.set_list([1, 2, 3])
my_list.list()  # [1, 2, 3]
db.add(...)
db.all()
db.get(...)
db.add(...)
db.save()
db.delete(...)
@get('/a/1'):
    respond('Hello from A1')
    
@get('/a/2'):
    respond('Hello from A2') 

 include module_b 
@get('/b/1'):
    respond('Hello from B1')
    
@get('/b/2'):
    respond('Hello from B2')
@get('/a/1'):
    respond('Hello from A1')
    
@get('/a/2'):
    respond('Hello from A2') 

@get('/b/1'):
    respond('Hello from B1')
    
@get('/b/2'):
    respond('Hello from B2')
- main.ask
    - modules/
        - module_b.ask
include modules.module_b
[db]
path = 'database/my_db.db'
[db]
custom = true
path = 'mysql://username:password@server/db'
[rules]
underscores = false
[system]
keep_app = false
[system]
server = false
[system]
output_path = 'custom/my_app.py'
[select query object].db.sort(db.[desc/asc]([column]))
&basic
db_model MyModel:
    id = db.col(db.int, db.pk)
    number = db.col(db.int)
    column = db.col(...)
...

# Selects all rows in MyModel where the column "coumn" is set to "text".
# Sorts the rows in descending order by the column "number".
results = MyModel.db.get_by(column='text').db.sort(db.desc(MyModel.number))

...
extend module
Add
Select
Select
Select
this page
Delete
my_list = db.list(['a', 'b', 'c'])
my_list.list()  # ['a', 'b', 'c']
my_list = db.list(['a', 'b', 'c'])
my_list.get(1)  # 'b'
my_list = db.list(['a', 'b', 'c'])
my_list.push('d')
my_list.list()  # ['a', 'b', 'c', 'd']
my_list = db.list(['a', 'b', 'c'])
my_list.remove(1)
my_list.list()  # ['a', 'c']

Random Generators

Generate pseudo-random numbers and choices.

Use the different methods of the built-in random object for this.

Int()

random.int(min, max, count)

Parameters

  • An integer.

  • The start of the range.

  • An integer.

  • The end of the range.

  • Optional parameter (default is 1)

  • How many random numbers to generate.

  • An integer.

Float()

random.float(min, max, count, decimals, unique)

Parameters

  • A float.

  • The start of the range.

  • A float.

  • The end of the range.

  • Optional parameter (default is 1)

  • How many random numbers to generate.

  • An integer.

  • Optional parameter (default is 16)

  • How many decimals (max) to include.

  • A boolean (True/False).

  • Should the numbers generated be unique?

Element()

random.element(iterable, count, weights, unique)

Parameters

  • The iterable to get random elements from.

  • E.g. a list.

  • Optional parameter (default is 1)

  • How many random choices/elements to generate.

  • An integer.

  • Optional weights for the generation.

  • A list.

  • A boolean (True/False).

  • Should the generated choices/elements be unique?

How to Create a Basic Login System

How to set up a basic login system in your Ask application.

This guide will walk you through setting up and testing an email & password-based JWT login system.

Please make sure that you are familiar with the basics of Ask including the following topics before reading this guide:

  • Decorators.

Set up

Create a User Database Model.

Create a Signup Route

Create a Login Route

Create a Test Route

Try it out

Use .e.g postman to first sign up, and then login. Then make a request to /auth_status and send the token you received from /login as a query parameter called token. If you're using Postman do the following:

  1. Open the Authorization tab.

  2. Select API key in the Type dropdown list.

  3. Set Key to token.

  4. Paste in your token into the Value field.

  5. Select Query Params in the Add To dropdown list.

You should receive the message "You are logged in!".

.

.

&basic
db_model User:
    id = db.col(db.int, db.pk)
    email = db.col(db.str(100), db.unique)
    password = db.col(db.str(256))
@post('/user/signup'):
    if require_keys(['email', 'password'], body):
        status('Missing required parameters', 400)
    
    user_check = db.get_by(email=body['email']).first()
    if db.exists(user):
        status('Email already registered', 400)
        
    new_user = User(body['email'], hash.hash(body['password']))
    db.add(new_user)
    
    respond({
        message: 'Success',
        user: new_user.s()
    })
    
@post('/user/login'):
    if require_keys(['email', 'password'], body):
        status('Missing required parameters', 400)
    
    user = db.get_by(email=body['email']).first()
    if not db.exists(user):
        status('Email not registered', 400)
    
    if not hash.check(user.password, body['password']):
        status('Wrong password', 400)
    
    # The token will be valid for one hour (3600 seconds).
    auth.login(body['email'], 3600)

    respond(auth.get_token())
&protected
@get('/auth_status'):
    respond('You are logged in!')
Routes & Requests
Databases
Hashing

Properties & Methods of _auth

Properties and methods.

auth is a built-in object. It's used for, generating & verifying tokens. It also has a few other useful methods and properties.

secret_key

Specify the secret key used for encoding/decoding tokens. This is by default a randomly generated UUID string.

Usage

auth.secret_key = 'secret key goes here'

login()

Generates a JWT.

Usage

auth.login([user], [expiry])

Parameters:

  • String.

  • Typically the users email address or username.

  • Integer.

  • Seconds.

  • How many seconds is the token valid for. E.g. 3600 == one hour.

decode()

Returns the payload for the current token. Use this function inside routes decorated with &protected. You can e.g. use this function to get the email/username from the token (the user parameter passed into login()).

  • Returns a dictionary.

Usage

auth.decode()

encode() Advanced!

Basically the same as login(), but you provide the payload to be encoded, while login() takes a username/email and automatically converts it into a payload dictionary with an expiration value. Use encode() if you want to encode more data than just a username and an expiration.

When using encode() you have to create at least a key called exp in the payload dictionary that holds the current timestamp (Unix epoch seconds) as it's value.

Example:exp: datetime.datetime.utcnow() + datetime.timedelta(seconds=expiry).

Usage

auth.encode(payload)

Parameters:

  • Dictionary.

  • Put data to be encoded into the token here. This data can be obtained later with the decode() method.

  • Example:

{
    user: '[email protected]',
    exp: datetime.datetime.utcnow() + datetime.timedelta(seconds=expiry)
}

is_valid()

Returns True if the current token is still valid.

Usage

auth.is_valid() # True or False

get_token()

Returns the token sent in the request as a string.

Usage

auth.get_token()

user()

Returns the user/username/email of the current authenticated user's token, you can also access this via the decode method _auth.decode()['user'].

This method only works if the token payload has a key called user, it will have this if you used the login() method to generate the token, but if you generated the token with the encode() method, then this might not work.

Usage

auth.get_token()

Introduction

Documentation for Ask version 1.5.0

Introduction.

Ask is an open source, dynamic, and transpiled programming language built for building backends and APIs. Ask directly transpiles to Python, more specifically Flask.

Feature Highlights

  • Built-in JWT Authentication.

  • Super Simple Database Management.

  • Syntax Inspired by Python.

  • Built-in CORS Support.

  • Reduces Boilerplate.

  • Compatible with Python*

* = You can import external Python modules and call them from you Ask code.

Easy to Learn

Ask's syntax is heavily inspired by Python, and can almost be considered to be a superset of Python. This means that picking up Ask is super easy if you’re already familiar with Python.

The main idea behind Ask is to simplify common backend actions (e.g. working with databases). Building a full database CRUD REST API with JWT authentication in Ask is very straight forward and simple and requires virtually zero lines of boilerplate code and no setup whatsoever.

Extendable.

Ask is a transpiled language (kind of like TypeScript) which means that it compiles the source code to another language that has a similar level of abstraction. In Ask's case, the target language is Python, more specifically a Flask app.

Flask is a very popular and well-established web framework for Python, so there's already a lot of tools, and services for deploying Flask apps.

The transpiled app is completely standalone and doesn't require Ask in any way.

Example (Ask vs Flask)

Here is the same basic app with one GET route written in Ask and in Python with Flask.

Ask

products = [
  {
    name: 'Product 1',
    price: 30.0,
    qty: 300
  },
  {
    name: 'Product 2',
    price: 15.5,
    qty: 20
  }
]

@get('/api/v1/products'):
  respond({products: products})

Flask

This is what the same application would look like in Flask.

from flask import Flask, jsonify

app = Flask(__name__)

products = [
  {
    'name': 'Product 1',
    'price': 30.0,
    'qty': 300
  },
  {
    'name': 'Product 2',
    'price': 15.5,
    'qty': 20
  }
]

@app.route('/api/v1/products', methods=['GET'])
def get_products():
  return jsonify({'products': products})

if __name__ == '__main__':
  app.run()

As you can see Ask hides away all the clutter and boilerplate.

Serialize()

Easily serialize a list of database query objects.

Automatic serialization of database queries. Returns a list.

Usage

Parameters

  • A list.

    • Of database query objects.

To be able to use serialize() your database models need to have their own serialization method called: s() (like in the examples in this documentation), or use the &basic decorator.

Example

Ask
serialize(...)
db_model User:
    id = _db.col(_db.int, _db.pk)
    username = _db.col(_db.str(100))
    password = _db.col(_db.str(256))
    fname = _db.col(_db.str(100))
    sname = _db.col(_db.str(100))

    def init(self, username, password, fname, sname):
    self.username = username
    self.password = hash.hash(password)
    self.fname = fname
    self.sname = sname

    def s(self):
        return {
            username: self.username,
            name: self.fname + ' ' + self.sname
        }

@get('/users'):
    users = User.db.all()
    respond(serialize(users))