Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Documentation for Ask version 1.5.0
Ask is an open source, dynamic, and transpiled programming language built for building backends and APIs. Ask directly transpiles to Python, more specifically Flask.
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.
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.
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.
Here is the same basic app with one GET route written in Ask and in Python with Flask.
This is what the same application would look like in Flask.
As you can see Ask hides away all the clutter and boilerplate.
How to define routes.
You define routes withe the @ character followed by an (e.g. GET, POST, PUT, etc.). Routes work like functions.
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.
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).
How to install 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 , and more about virtual environments .
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).
CORS can be configured in your projects . Configuration options are added in the [cors]
section.
The configuration options supported are the same as Flask-Cors supports. Flask-Cors documentation:
Beginners guide to Ask
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 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.
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!
Cheatsheet with HTTP status codes and HTTP methods.
100 Continue.
101 Switching Protocols.
102 Processing.
103 Early Hints.
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
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
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
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
GET
HEAD
POST
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
Use the respond()
function (or the deprecated respond
keyword) to return JSON data. This will also include the 200 OK.
Use the status()
function to return JSON data with an .
The same as .
Integer.
.
Find a dictionary in a list of dictionaries by value.
Easily serialize a list of database query objects.
Generate pseudo-random numbers and choices.
Use the different methods of the built-in random
object for this.
Int()
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()
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()
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?
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.
Use Python's own built-in re
library for regexes.
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.
Call Abnex's different functions on the pre-imported ab
object.
Read more about Abnex .
Send email from you Ask app
You configure the mail tool from your projects . In this example we're connecting to gmail.
You also have to enable IMAP, and sometimes complete the steps here:
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.
A list.
of strings.
A dictionary
The dictionary to search for the required keys in.
E.g. body
.
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.
Models are built up of three parts (two are required and one is optional).
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.
All functions, variables, etc. used when working with databases are called on the built-in global db
variable.
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.
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.
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.
pk : Primary key.
unique: Unique.
nullable: Nullable.
basic_ignore: Ignore the column in the basic decorator code.
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.
It's good practice to call the serialization method s()
, that way you can also utilize the built-in function.
Selecting row(s).
There are three ways of selecting rows.
Selecting all rows that match a filter.
all()
This will select all rows in the table the MyModel model uses.
get()
This will select the row in the table that has the id/primary key 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.
get_by()
This will select all rows in the table with the same email address in the email
column.
This will return an array. You can get the first match by calling .first()
after the get_by()
method.
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).
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:
The init()
method assigns all columns a value that gets passed into the method.
The s()
method returns all columns values as separate key-value pairs.
The decorator should be placed on the line before the model definition.
Without using the &basic
decorator.
Using the &basic
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.
First, select a row and store it in a variable. Read more about selecting .
First, select a row and store it in a variable. Read more about selecting .
Create (add), Read (select), Update, Delete.
db.add()
A database model instance.
db.all()
- None
db.get()
The id/primary key
Same datatype as the primary key in the model (usually an integer).
db.get_by()
A series of named parameters.
db.get_by(column=value, other_column=other_value)
db.save()
- None
db.delete()
Query object
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.
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.
This will automatically also add the row to the database.
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:
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).
We are using PyJWT behind the scenes, which means that Ask's authentication is as secure as the PyJWT library.
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".
How to sort selection query results.
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.
Decorators.
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:
Open the Authorization
tab.
Select API key in the Type
dropdown list.
Set Key
to token
.
Paste in your token into the Value
field.
Select Query Params
in the Add To
dropdown list.
You should receive the message "You are logged in!".
.
.
A query object of a database row, returned by the different .
How to send a JWT token to protected routes.
You can pass the JWT token along with your request as a query parameter called token
there's some more information available in chapter.
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.
login()
Generates a JWT.
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.
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)
.
Dictionary.
Put data to be encoded into the token here. This data can be obtained later with the decode()
method.
Example:
is_valid()
Returns True if the current token is still valid.
get_token()
Returns the token sent in the request as a string.
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']
.
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.
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.
The output of this program would be:
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".
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
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:
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.
Limit the number of requests the same IP can make to a route. .
Automatically generate the standard database model structure with an initialization and serialization method. .
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.
String.
The name of the environment variable.
How to secure routes.
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 (/).
Seconds.
Minutes.
Hours.
Days.
Years
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.
Typically a string.
check()
Check if a value corresponds with a hash. Used for e.g. verifying passwords. Returns True/False.
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:
But using .check
is a bit easier to both read and write.
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.
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.]
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.
The file is a .toml file and is grouped by sections that contain rules.
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'
.
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.
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.
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.
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.
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
.
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.
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.
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.
main.ask
module_b.ask
(Result) (app.ask)
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)
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.
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 first number increases we recommend that you always read the release notes as there might be breaking changes!
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.
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.
-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.
Are you interested in contributing to our awesome FOSS project? Please visit Ask's GitHub page for more information.
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 ) or (preferably) by making an issue report on .