atila


Nameatila JSON
Version 0.27.0 PyPI version JSON
download
home_pagehttps://gitlab.com/sitai/atila
SummaryAtila Framework
upload_time2024-04-13 10:36:24
maintainerNone
docs_urlNone
authorHans Roh
requires_pythonNone
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Atila

*Atila* is **life-cycle hook based web framework** which is run on [Skitai WSGI App Engine](https://pypi.org/project/skitai/).

```python
# myservice/__init__.py
def __app__ ():
    from atila import Atila
    return Atila (__name__)

def __mount__ (context, app):
    @app.route ("/")
    def index (context):
        return "Hello, World"
```

```python
# skitaid.py
import skitai
import myservice

skitai.mount ("/", myservice)
skitai.run ()
```

And run,
```python
python3 skitaid.py
```
Now, `http://localhost:5000/` is working.







# Life-Cycle Hook Based Implementation

Users can use some special hook functions like `__app__ ()`, `__mount__ ()` and so on.

This hooks can be integrated your existing source codes without any side effects.
You just add routed controllers and then it could work as API backend.








# Important Notice

*CAUTION*: Atila is base on WSGI but can be run only
with [Skitai App Engine](https://pypi.org/project/skitai/)
because it crosses the border of WSGI for async router.

This means if you make your Atila app, you have no choice
but Skitai as WSGI app server. And Atila's unique and unconventional
style may become very hard work to port to other framework.







# Async/Await Support

Atila almost fully support `async/await` manner.

Briefly Atila has 2 event loops for:
- handling requests with `asyncore` as main loop
- running async functions with `asyncio` as executor

I still recommend use `sync` funtions mainly unless massive
I/O related tasks or have no choice.




# Table of Content
- **[Atila](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#atila)**
- **[Life-Cycle Hook Based Implementation](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#life-cycle-hook-based-implementation)**
- **[Important Notice](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#important-notice)**
- **[Async/Await Support](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#asyncawait-support)**
- **[Installation](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#installation)**
- **[Before You Start](https://gitlab.com/skitai/atila/-/blob/master/docs/005.before-you-start.md#before-you-start)**
  - Getting IDE Suggestions
- **[Quick Start](https://gitlab.com/skitai/atila/-/blob/master/docs/010.quick-start.md#quick-start)**
  - Dumb Seek
  - Unit Testing
  - Write Atila App Hooks
  - Add Launch Script
  - Add Analyzing API Endpoint
  - Add Indexing and Searching API Endpoints
  - Testing API
  - Final Source Codes
  - Conclusion
- **[Connecting Database](https://gitlab.com/skitai/atila/-/blob/master/docs/015.database.md#connecting-database)**
  - Option I: Database Management Not Required
  - Option II: Database Management Required: Django As ORM
    - Initialize Django Project
    - Auto Reloading For Development
    - Add Django App
    - Django Rest Framework
    - Working With Atila App
  - Conclusion
- **[App Life Cycle and Hooks](https://gitlab.com/skitai/atila/-/blob/master/docs/020.life-cycle-and-hooks.md#app-life-cycle-and-hooks)**
  - App Life Cycle and Hooks
    - Runtine Preference and Bootstrapping
  - Request Life Cycle and Hooks
- **[App Middlewares](https://gitlab.com/skitai/atila/-/blob/master/docs/022.middleware.md#app-middlewares)**
- **[Context](https://gitlab.com/skitai/atila/-/blob/master/docs/030.context.md#context)**
  - Global Context
    - Proto Context
    - Thread Context
  - Request Context
    - Request Context
    - Cloned Context
  - Registering Context Scope Objects and Methods
- **[Routing](https://gitlab.com/skitai/atila/-/blob/master/docs/032.routing.md#routing)**
  - Routing
  - URL Parameter Types
    - String (Default)
    - Integer
    - Float
    - Path
  - Request Parameters
  - Async Routing
- **[Request Parameters](https://gitlab.com/skitai/atila/-/blob/master/docs/035.request-parameter.md#request-parameters-1)**
  - URL / Body Parameter
  - Parameter Validation
    - Inline Validation
    - Class Validation
    - Django Model Based Validation
    - Functional Validation
    - More Validators
    - Validation Operators
  - Getting Argument Specifications
- **[Processing Request](https://gitlab.com/skitai/atila/-/blob/master/docs/040.request.md#processing-request)**
  - Request Object
    - Basic Members
    - Basic Methods
    - Route Options
  - Environment Variables
    - In The Template Engine
  - App & Request Gloabal
  - File Upload
  - Cookie
  - Session
    - Namespaced Session
  - Message Box
  - Route Name Based Call
  - Helpers
    - Conditional Prework
    - Checking Dependencies
- **[Making Response](https://gitlab.com/skitai/atila/-/blob/master/docs/050.response.md#making-response)**
  - Cache Control
  - HTTP Error
  - Primitive
    - String
    - API Response
    - Rendered Template
    - render_or_API
    - File
    - Static
    - Generator
    - Redirecting
    - RPC Response
    - Threaded Data Streaming
  - Building URL
- **[Websocket](https://gitlab.com/skitai/atila/-/blob/master/docs/100.websocket.md#websocket)**
  - Specifications
    - WS_STREAM
    - WS_SESSION
      - Opening/Closing Hooks
  - WebSocket Piped Process
- **[gRPC](https://gitlab.com/skitai/atila/-/blob/master/docs/110.grpc.md#grpc)**
  - Unary RPC
    - Async version
  - Async Streaming RPC
    - Response Streaming
    - Request Streaming
    - Bidirectional Streaming
- **[Access Control and Authentication](https://gitlab.com/skitai/atila/-/blob/master/docs/200.authentification.md#access-control-and-authentication)**
  - CORS (Cross Origin Resource Sharing) and Preflight
  - Custom Authentication
  - WWW-Authentication
    - Authentication On Specific Methods
    - Password Provider
    - Authentication On Entire App
  - Bearer Authentication
  - Test Passing
- **[Security and Token](https://gitlab.com/skitai/atila/-/blob/master/docs/205.security-and-token.md#security-and-token)**
  - Cross Site Request Forgery Token (CSRF Token)
  - JWT Token
  - One-Time Password
  - One-Time Token
- **[Event Bus](https://gitlab.com/skitai/atila/-/blob/master/docs/210.event-bus.md#event-bus)**
  - Request Life Cycle Events
- **[Interval Base App Maintenancing](https://gitlab.com/skitai/atila/-/blob/master/docs/220.scheduled-maintain.md#interval-base-app-maintenancing)**
- **[Multiple GPUs Allocation To Workers](https://gitlab.com/skitai/atila/-/blob/master/docs/230.gpu-allocation.md#multiple-gpus-allocation-to-workers)**
- **[Using Task](https://gitlab.com/skitai/atila/-/blob/master/docs/240.task.md#using-task)**
  - Thread / Process / Subrocess
  - Tasks
- **[HTTP/2,3 Server Pushing](https://gitlab.com/skitai/atila/-/blob/master/docs/250.server-push.md#http23-server-pushing)**
- **[Logging and Traceback](https://gitlab.com/skitai/atila/-/blob/master/docs/260.logging.md#logging-and-traceback)**
- **[Template Engine](https://gitlab.com/skitai/atila/-/blob/master/docs/340.template-engine.md#template-engine)**
  - Customizing Jinja2
  - Custom Error Templates
- **[Using Requests](https://gitlab.com/skitai/atila/-/blob/master/docs/360.requests.md#using-requests)**
- **[Allied App](https://gitlab.com/skitai/atila/-/blob/master/docs/500.allied-app.md#allied-app)**
  - Conclusion
- **[Working With Multiple Apps](https://gitlab.com/skitai/atila/-/blob/master/docs/510.multiple-apps.md#working-with-multiple-apps)**
  - Event Subscription
  - Data Exchanging
  - Accesing Other App Directly
- **[Test Client](https://gitlab.com/skitai/atila/-/blob/master/docs/700.test-client.md#test-client)**
  - Integrating pytest and API Documentation
- **[Debugging](https://gitlab.com/skitai/atila/-/blob/master/docs/710.debugging.md#debugging)**
  - VS Code
- **[Deployment](https://gitlab.com/skitai/atila/-/blob/master/docs/720.deployment.md#deployment)**
  - Using Systemctl
  - Using AWS ECS (Elastic Container Service)
- **[Change Log](https://gitlab.com/skitai/atila/-/blob/master/docs/900.change-log.md#change-log)**













# Installation

**Requirements**

Python 3.7+


**Installation**

Atila and other core base dependent libraries is developing on
single milestone, install/upgrade all at once. Otherwise it is
highly possible to meet some errors.

With pip
```bash
pip3 install -U atila skitai rs4
```

Optional required as you need,
```bash
pip3 install protobuf # for GRPC
```



**[<< Back To README](https://gitlab.com/skitai/atila/-/blob/master/README.md)**












# Before You Start

## Getting IDE Suggestions

Type hinting enables IDE suggestions.

```python
from atila import Atila, Context

def __mount__ (context: Context, app: Atila):

    @app.route ("/ping")
    def ping (context: Context):
        return 'pong'
```

In this document, type hinting is not used for code readability.










# Quick Start
For exmaple, you make local dumb search engine named as `dumbseek`.



## Dumb Seek
Your package structure is like this.

```shell
dumbseek/
    index/
        __init__.py
        indexer.py
        searcher.py
        db.py
    __init__.py
    analyzer.py
```

File `dumbseek/__init__.py`
```python
__version__ = "1.0"

NAME = "Dumb Seek"
```

File `dumbseek/analyzer.py`
```python
def analyze (query):
    return query.lower ().split ()
```

File `dumbseek/index/__init__.py` is *empty*.


File `dumbseek/index/db.py`
```python
INVERTED_INDEX = {}
DOCUMENTS = {}
```

File `dumbseek/index/indexer.py`
```python
from .. import analyzer
from . import db

def index (doc):
    doc_ids = list (db.DOCUMENTS.keys ())
    if not doc_ids:
        doc_id = 0
    else:
        doc_id = max (doc_ids) + 1

    db.DOCUMENTS [doc_id] = doc
    for token in analyzer.analyze (doc):
        if token not in db.INVERTED_INDEX:
            db.INVERTED_INDEX [token] = set ()
        db.INVERTED_INDEX [token].add (doc_id)
    return doc_id
```

File `dumbseek/index/searcher.py`
```python
from .. import analyzer
from . import db

def search (query):
    results = None
    for token in analyzer.analyze (query):
        if token not in db.INVERTED_INDEX:
            return []
        doc_ids = db.INVERTED_INDEX.get (token, set ())
        if results is None:
            results = doc_ids
            continue
        results = results.intersection (doc_ids)
    return [db.DOCUMENTS [doc_id] for doc_id in sorted (list (results))]
```







## Unit Testing
For more clarify, pytest example is:
```python
import os
import sys; sys.path.insert (0, '../examples/dumbseek')
import dumbseek
from dumbseek import analyzer
from dumbseek.index import indexer, searcher, db

def test_analyze ():
    assert analyzer.analyze ('Detective Holmes') == ['detective', 'holmes']

def test_index ():
    assert indexer.index ('Detective Holmes') == 0
    assert db.DOCUMENTS [0] == 'Detective Holmes'
    assert db.INVERTED_INDEX ['detective'] == {0}
    assert indexer.index ('Detective Monk') == 1
    assert db.INVERTED_INDEX ['monk'] == {1}
    assert searcher.search ('detective holmes') == ['Detective Holmes']
```








## Write Atila App Hooks

Now, you find `dumbseek` is useful than you think, you have plan to
serve with RESTful API.

Add `__app__` and `__mount__` hooks into `dumbseek/__init__.py`.
```python
__version__ = "1.0"

NAME = "Dumb Seek"

# atila hooks ------------------------
def __app__ ():
    from atila import Atila
    return Atila (__name__)

def __mount__ (context, app):
    @app.route ("/")
    def index (context):
        return context.API (app = NAME)
```







## Add Launch Script
For testing, you have to create launch script.

```python
# skitaid.py
import skitai
import dumbseek

if __name__ == '__main__':
    with skitai.preference () as pref:
        skitai.mount ('/', dumbseek, pref)
    skitai.run (port = 5000, name = 'dumbseek')
```

```shell
python3 skitaid.py --devel
```
Now, `http://localhost:5000/` is working.








## Add Analyzing API Endpoint

We have to create endpoint `POST /api/tokens`.
Add `__mount__` hook to `dumbseek/analyzer.py`.

```python
def analyze (query):
    return query.lower ().split ()

# atila hooks ------------------------
def __mount__ (context, app):
    @app.route ("/tokens", methods = ["POST", "OPTIONS"])
    def tokens (context, query):
        return context.API (result = analyze (query))
```

And for mounting to app, add `__setup__` hook to `dumbseek/__init__.py`.
```python
def __setup__ (context, app):
    from . import analyzer
    app.mount ("/api", analyzer)

def __mount__ (context, app):
    ...
```
`http://localhost:5000/api/tokens` is working.








## Add Indexing and Searching API Endpoints

We have to create 3 endpoints:
- `POST /api/documents` for indexing document
- `GET /api/documents` for searching document
- `GET /api/documents/<int:doc_id>` for geting document


Add `__mount__` to `dumbseek/index/__init__.py`.
```python
# atila hooks ------------------------
def __mount__ (context, app):
    from . import indexer
    from . import searcher

    @app.route ("/documents", methods = ["POST", "OPTIONS"])
    def index_document (context, document):
        doc_id = indexer.index (document)
        return context.API (
            "201 Created",
            url = context.urlfor (get_document, doc_id)
        )

    @app.route ("/documents", methods = ["GET"])
    def search_document (context, q):
        return context.API (
            result = searcher.search (q)
        )

    @app.route ("/documents/<int:doc_id>", methods = ["GET"])
    def get_document (context, doc_id):
        try:
            return context.API (
                document = db.DOCUMENTS [doc_id]
            )
        except KeyError:
            raise context.HttpError ("404 Not Found")
```

And mount to app, add `__setup__` hook to `dumbseek/__init__.py`.
```python
def __setup__ (context, app):
    from . import analyzer
    from . import index
    app.mount ("/api", analyzer)
    app.mount ("/api", index)

def __mount__ (context, app):
    ...
```
`http://localhost:5000/api/documents` is working.










## Testing API
```python
import pytest
from functools import partial
import skitai
from atila.pytest_hooks import *

@pytest.fixture
def launch ():
    return partial (skitai.test_client, port = 30371, silent = False)

def test_api (launch):
    with launch ('../examples/dumbseek/skitaid.py') as engine:
        r = engine.get ('/')
        assert r.json () ['app'] == 'Dumb Seek'

        r = engine.post ('/api/tokens', data = {'query': 'Detective Holmes'})
        assert r.json () ['result'] == ['detective', 'holmes']

        r = engine.post ('/api/documents', data = {'document': 'Detective Holmes'})
        assert r.status_code == 201
        assert r.json () ['url'] == '/api/documents/0'

        r = engine.get ('/api/documents', params = {'q': 'Detective Holmes'})
        assert r.json () ['result'] == ['Detective Holmes']

        r = engine.get ('/api/documents/0')
        assert r.json ()['document'] == 'Detective Holmes'

        r = engine.post ('/api/documents', data = {'document': 'Detective Monk'})

        r = engine.get ('/api/documents', params = {'q': 'detective'})
        assert r.json () ['result'] == ['Detective Holmes', 'Detective Monk']
```





## Final Source Codes
See https://gitlab.com/skitai/atila/-/tree/master/examples/dumbseek









## Conclusion
- We add REST API to our existing source codes without any side effects
- We can still use `dumbseek` as local library
- Just add `skitaid.py`, we can serve as RESTful API online











**[<< Back To README](https://gitlab.com/skitai/atila/-/blob/master/README.md)**













            

Raw data

            {
    "_id": null,
    "home_page": "https://gitlab.com/sitai/atila",
    "name": "atila",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": null,
    "author": "Hans Roh",
    "author_email": "hansroh@gmail.com",
    "download_url": "https://pypi.python.org/pypi/atila",
    "platform": "posix",
    "description": "# Atila\n\n*Atila* is **life-cycle hook based web framework** which is run on [Skitai WSGI App Engine](https://pypi.org/project/skitai/).\n\n```python\n# myservice/__init__.py\ndef __app__ ():\n    from atila import Atila\n    return Atila (__name__)\n\ndef __mount__ (context, app):\n    @app.route (\"/\")\n    def index (context):\n        return \"Hello, World\"\n```\n\n```python\n# skitaid.py\nimport skitai\nimport myservice\n\nskitai.mount (\"/\", myservice)\nskitai.run ()\n```\n\nAnd run,\n```python\npython3 skitaid.py\n```\nNow, `http://localhost:5000/` is working.\n\n\n\n\n\n\n\n# Life-Cycle Hook Based Implementation\n\nUsers can use some special hook functions like `__app__ ()`, `__mount__ ()` and so on.\n\nThis hooks can be integrated your existing source codes without any side effects.\nYou just add routed controllers and then it could work as API backend.\n\n\n\n\n\n\n\n\n# Important Notice\n\n*CAUTION*: Atila is base on WSGI but can be run only\nwith [Skitai App Engine](https://pypi.org/project/skitai/)\nbecause it crosses the border of WSGI for async router.\n\nThis means if you make your Atila app, you have no choice\nbut Skitai as WSGI app server. And Atila's unique and unconventional\nstyle may become very hard work to port to other framework.\n\n\n\n\n\n\n\n# Async/Await Support\n\nAtila almost fully support `async/await` manner.\n\nBriefly Atila has 2 event loops for:\n- handling requests with `asyncore` as main loop\n- running async functions with `asyncio` as executor\n\nI still recommend use `sync` funtions mainly unless massive\nI/O related tasks or have no choice.\n\n\n\n\n# Table of Content\n- **[Atila](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#atila)**\n- **[Life-Cycle Hook Based Implementation](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#life-cycle-hook-based-implementation)**\n- **[Important Notice](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#important-notice)**\n- **[Async/Await Support](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#asyncawait-support)**\n- **[Installation](https://gitlab.com/skitai/atila/-/blob/master/docs/000.preface.md#installation)**\n- **[Before You Start](https://gitlab.com/skitai/atila/-/blob/master/docs/005.before-you-start.md#before-you-start)**\n  - Getting IDE Suggestions\n- **[Quick Start](https://gitlab.com/skitai/atila/-/blob/master/docs/010.quick-start.md#quick-start)**\n  - Dumb Seek\n  - Unit Testing\n  - Write Atila App Hooks\n  - Add Launch Script\n  - Add Analyzing API Endpoint\n  - Add Indexing and Searching API Endpoints\n  - Testing API\n  - Final Source Codes\n  - Conclusion\n- **[Connecting Database](https://gitlab.com/skitai/atila/-/blob/master/docs/015.database.md#connecting-database)**\n  - Option I: Database Management Not Required\n  - Option II: Database Management Required: Django As ORM\n    - Initialize Django Project\n    - Auto Reloading For Development\n    - Add Django App\n    - Django Rest Framework\n    - Working With Atila App\n  - Conclusion\n- **[App Life Cycle and Hooks](https://gitlab.com/skitai/atila/-/blob/master/docs/020.life-cycle-and-hooks.md#app-life-cycle-and-hooks)**\n  - App Life Cycle and Hooks\n    - Runtine Preference and Bootstrapping\n  - Request Life Cycle and Hooks\n- **[App Middlewares](https://gitlab.com/skitai/atila/-/blob/master/docs/022.middleware.md#app-middlewares)**\n- **[Context](https://gitlab.com/skitai/atila/-/blob/master/docs/030.context.md#context)**\n  - Global Context\n    - Proto Context\n    - Thread Context\n  - Request Context\n    - Request Context\n    - Cloned Context\n  - Registering Context Scope Objects and Methods\n- **[Routing](https://gitlab.com/skitai/atila/-/blob/master/docs/032.routing.md#routing)**\n  - Routing\n  - URL Parameter Types\n    - String (Default)\n    - Integer\n    - Float\n    - Path\n  - Request Parameters\n  - Async Routing\n- **[Request Parameters](https://gitlab.com/skitai/atila/-/blob/master/docs/035.request-parameter.md#request-parameters-1)**\n  - URL / Body Parameter\n  - Parameter Validation\n    - Inline Validation\n    - Class Validation\n    - Django Model Based Validation\n    - Functional Validation\n    - More Validators\n    - Validation Operators\n  - Getting Argument Specifications\n- **[Processing Request](https://gitlab.com/skitai/atila/-/blob/master/docs/040.request.md#processing-request)**\n  - Request Object\n    - Basic Members\n    - Basic Methods\n    - Route Options\n  - Environment Variables\n    - In The Template Engine\n  - App & Request Gloabal\n  - File Upload\n  - Cookie\n  - Session\n    - Namespaced Session\n  - Message Box\n  - Route Name Based Call\n  - Helpers\n    - Conditional Prework\n    - Checking Dependencies\n- **[Making Response](https://gitlab.com/skitai/atila/-/blob/master/docs/050.response.md#making-response)**\n  - Cache Control\n  - HTTP Error\n  - Primitive\n    - String\n    - API Response\n    - Rendered Template\n    - render_or_API\n    - File\n    - Static\n    - Generator\n    - Redirecting\n    - RPC Response\n    - Threaded Data Streaming\n  - Building URL\n- **[Websocket](https://gitlab.com/skitai/atila/-/blob/master/docs/100.websocket.md#websocket)**\n  - Specifications\n    - WS_STREAM\n    - WS_SESSION\n      - Opening/Closing Hooks\n  - WebSocket Piped Process\n- **[gRPC](https://gitlab.com/skitai/atila/-/blob/master/docs/110.grpc.md#grpc)**\n  - Unary RPC\n    - Async version\n  - Async Streaming RPC\n    - Response Streaming\n    - Request Streaming\n    - Bidirectional Streaming\n- **[Access Control and Authentication](https://gitlab.com/skitai/atila/-/blob/master/docs/200.authentification.md#access-control-and-authentication)**\n  - CORS (Cross Origin Resource Sharing) and Preflight\n  - Custom Authentication\n  - WWW-Authentication\n    - Authentication On Specific Methods\n    - Password Provider\n    - Authentication On Entire App\n  - Bearer Authentication\n  - Test Passing\n- **[Security and Token](https://gitlab.com/skitai/atila/-/blob/master/docs/205.security-and-token.md#security-and-token)**\n  - Cross Site Request Forgery Token (CSRF Token)\n  - JWT Token\n  - One-Time Password\n  - One-Time Token\n- **[Event Bus](https://gitlab.com/skitai/atila/-/blob/master/docs/210.event-bus.md#event-bus)**\n  - Request Life Cycle Events\n- **[Interval Base App Maintenancing](https://gitlab.com/skitai/atila/-/blob/master/docs/220.scheduled-maintain.md#interval-base-app-maintenancing)**\n- **[Multiple GPUs Allocation To Workers](https://gitlab.com/skitai/atila/-/blob/master/docs/230.gpu-allocation.md#multiple-gpus-allocation-to-workers)**\n- **[Using Task](https://gitlab.com/skitai/atila/-/blob/master/docs/240.task.md#using-task)**\n  - Thread / Process / Subrocess\n  - Tasks\n- **[HTTP/2,3 Server Pushing](https://gitlab.com/skitai/atila/-/blob/master/docs/250.server-push.md#http23-server-pushing)**\n- **[Logging and Traceback](https://gitlab.com/skitai/atila/-/blob/master/docs/260.logging.md#logging-and-traceback)**\n- **[Template Engine](https://gitlab.com/skitai/atila/-/blob/master/docs/340.template-engine.md#template-engine)**\n  - Customizing Jinja2\n  - Custom Error Templates\n- **[Using Requests](https://gitlab.com/skitai/atila/-/blob/master/docs/360.requests.md#using-requests)**\n- **[Allied App](https://gitlab.com/skitai/atila/-/blob/master/docs/500.allied-app.md#allied-app)**\n  - Conclusion\n- **[Working With Multiple Apps](https://gitlab.com/skitai/atila/-/blob/master/docs/510.multiple-apps.md#working-with-multiple-apps)**\n  - Event Subscription\n  - Data Exchanging\n  - Accesing Other App Directly\n- **[Test Client](https://gitlab.com/skitai/atila/-/blob/master/docs/700.test-client.md#test-client)**\n  - Integrating pytest and API Documentation\n- **[Debugging](https://gitlab.com/skitai/atila/-/blob/master/docs/710.debugging.md#debugging)**\n  - VS Code\n- **[Deployment](https://gitlab.com/skitai/atila/-/blob/master/docs/720.deployment.md#deployment)**\n  - Using Systemctl\n  - Using AWS ECS (Elastic Container Service)\n- **[Change Log](https://gitlab.com/skitai/atila/-/blob/master/docs/900.change-log.md#change-log)**\n\n\n\n\n\n\n\n\n\n\n\n\n\n# Installation\n\n**Requirements**\n\nPython 3.7+\n\n\n**Installation**\n\nAtila and other core base dependent libraries is developing on\nsingle milestone, install/upgrade all at once. Otherwise it is\nhighly possible to meet some errors.\n\nWith pip\n```bash\npip3 install -U atila skitai rs4\n```\n\nOptional required as you need,\n```bash\npip3 install protobuf # for GRPC\n```\n\n\n\n**[<< Back To README](https://gitlab.com/skitai/atila/-/blob/master/README.md)**\n\n\n\n\n\n\n\n\n\n\n\n\n# Before You Start\n\n## Getting IDE Suggestions\n\nType hinting enables IDE suggestions.\n\n```python\nfrom atila import Atila, Context\n\ndef __mount__ (context: Context, app: Atila):\n\n    @app.route (\"/ping\")\n    def ping (context: Context):\n        return 'pong'\n```\n\nIn this document, type hinting is not used for code readability.\n\n\n\n\n\n\n\n\n\n\n# Quick Start\nFor exmaple, you make local dumb search engine named as `dumbseek`.\n\n\n\n## Dumb Seek\nYour package structure is like this.\n\n```shell\ndumbseek/\n    index/\n        __init__.py\n        indexer.py\n        searcher.py\n        db.py\n    __init__.py\n    analyzer.py\n```\n\nFile `dumbseek/__init__.py`\n```python\n__version__ = \"1.0\"\n\nNAME = \"Dumb Seek\"\n```\n\nFile `dumbseek/analyzer.py`\n```python\ndef analyze (query):\n    return query.lower ().split ()\n```\n\nFile `dumbseek/index/__init__.py` is *empty*.\n\n\nFile `dumbseek/index/db.py`\n```python\nINVERTED_INDEX = {}\nDOCUMENTS = {}\n```\n\nFile `dumbseek/index/indexer.py`\n```python\nfrom .. import analyzer\nfrom . import db\n\ndef index (doc):\n    doc_ids = list (db.DOCUMENTS.keys ())\n    if not doc_ids:\n        doc_id = 0\n    else:\n        doc_id = max (doc_ids) + 1\n\n    db.DOCUMENTS [doc_id] = doc\n    for token in analyzer.analyze (doc):\n        if token not in db.INVERTED_INDEX:\n            db.INVERTED_INDEX [token] = set ()\n        db.INVERTED_INDEX [token].add (doc_id)\n    return doc_id\n```\n\nFile `dumbseek/index/searcher.py`\n```python\nfrom .. import analyzer\nfrom . import db\n\ndef search (query):\n    results = None\n    for token in analyzer.analyze (query):\n        if token not in db.INVERTED_INDEX:\n            return []\n        doc_ids = db.INVERTED_INDEX.get (token, set ())\n        if results is None:\n            results = doc_ids\n            continue\n        results = results.intersection (doc_ids)\n    return [db.DOCUMENTS [doc_id] for doc_id in sorted (list (results))]\n```\n\n\n\n\n\n\n\n## Unit Testing\nFor more clarify, pytest example is:\n```python\nimport os\nimport sys; sys.path.insert (0, '../examples/dumbseek')\nimport dumbseek\nfrom dumbseek import analyzer\nfrom dumbseek.index import indexer, searcher, db\n\ndef test_analyze ():\n    assert analyzer.analyze ('Detective Holmes') == ['detective', 'holmes']\n\ndef test_index ():\n    assert indexer.index ('Detective Holmes') == 0\n    assert db.DOCUMENTS [0] == 'Detective Holmes'\n    assert db.INVERTED_INDEX ['detective'] == {0}\n    assert indexer.index ('Detective Monk') == 1\n    assert db.INVERTED_INDEX ['monk'] == {1}\n    assert searcher.search ('detective holmes') == ['Detective Holmes']\n```\n\n\n\n\n\n\n\n\n## Write Atila App Hooks\n\nNow, you find `dumbseek` is useful than you think, you have plan to\nserve with RESTful API.\n\nAdd `__app__` and `__mount__` hooks into `dumbseek/__init__.py`.\n```python\n__version__ = \"1.0\"\n\nNAME = \"Dumb Seek\"\n\n# atila hooks ------------------------\ndef __app__ ():\n    from atila import Atila\n    return Atila (__name__)\n\ndef __mount__ (context, app):\n    @app.route (\"/\")\n    def index (context):\n        return context.API (app = NAME)\n```\n\n\n\n\n\n\n\n## Add Launch Script\nFor testing, you have to create launch script.\n\n```python\n# skitaid.py\nimport skitai\nimport dumbseek\n\nif __name__ == '__main__':\n    with skitai.preference () as pref:\n        skitai.mount ('/', dumbseek, pref)\n    skitai.run (port = 5000, name = 'dumbseek')\n```\n\n```shell\npython3 skitaid.py --devel\n```\nNow, `http://localhost:5000/` is working.\n\n\n\n\n\n\n\n\n## Add Analyzing API Endpoint\n\nWe have to create endpoint `POST /api/tokens`.\nAdd `__mount__` hook to `dumbseek/analyzer.py`.\n\n```python\ndef analyze (query):\n    return query.lower ().split ()\n\n# atila hooks ------------------------\ndef __mount__ (context, app):\n    @app.route (\"/tokens\", methods = [\"POST\", \"OPTIONS\"])\n    def tokens (context, query):\n        return context.API (result = analyze (query))\n```\n\nAnd for mounting to app, add `__setup__` hook to `dumbseek/__init__.py`.\n```python\ndef __setup__ (context, app):\n    from . import analyzer\n    app.mount (\"/api\", analyzer)\n\ndef __mount__ (context, app):\n    ...\n```\n`http://localhost:5000/api/tokens` is working.\n\n\n\n\n\n\n\n\n## Add Indexing and Searching API Endpoints\n\nWe have to create 3 endpoints:\n- `POST /api/documents` for indexing document\n- `GET /api/documents` for searching document\n- `GET /api/documents/<int:doc_id>` for geting document\n\n\nAdd `__mount__` to `dumbseek/index/__init__.py`.\n```python\n# atila hooks ------------------------\ndef __mount__ (context, app):\n    from . import indexer\n    from . import searcher\n\n    @app.route (\"/documents\", methods = [\"POST\", \"OPTIONS\"])\n    def index_document (context, document):\n        doc_id = indexer.index (document)\n        return context.API (\n            \"201 Created\",\n            url = context.urlfor (get_document, doc_id)\n        )\n\n    @app.route (\"/documents\", methods = [\"GET\"])\n    def search_document (context, q):\n        return context.API (\n            result = searcher.search (q)\n        )\n\n    @app.route (\"/documents/<int:doc_id>\", methods = [\"GET\"])\n    def get_document (context, doc_id):\n        try:\n            return context.API (\n                document = db.DOCUMENTS [doc_id]\n            )\n        except KeyError:\n            raise context.HttpError (\"404 Not Found\")\n```\n\nAnd mount to app, add `__setup__` hook to `dumbseek/__init__.py`.\n```python\ndef __setup__ (context, app):\n    from . import analyzer\n    from . import index\n    app.mount (\"/api\", analyzer)\n    app.mount (\"/api\", index)\n\ndef __mount__ (context, app):\n    ...\n```\n`http://localhost:5000/api/documents` is working.\n\n\n\n\n\n\n\n\n\n\n## Testing API\n```python\nimport pytest\nfrom functools import partial\nimport skitai\nfrom atila.pytest_hooks import *\n\n@pytest.fixture\ndef launch ():\n    return partial (skitai.test_client, port = 30371, silent = False)\n\ndef test_api (launch):\n    with launch ('../examples/dumbseek/skitaid.py') as engine:\n        r = engine.get ('/')\n        assert r.json () ['app'] == 'Dumb Seek'\n\n        r = engine.post ('/api/tokens', data = {'query': 'Detective Holmes'})\n        assert r.json () ['result'] == ['detective', 'holmes']\n\n        r = engine.post ('/api/documents', data = {'document': 'Detective Holmes'})\n        assert r.status_code == 201\n        assert r.json () ['url'] == '/api/documents/0'\n\n        r = engine.get ('/api/documents', params = {'q': 'Detective Holmes'})\n        assert r.json () ['result'] == ['Detective Holmes']\n\n        r = engine.get ('/api/documents/0')\n        assert r.json ()['document'] == 'Detective Holmes'\n\n        r = engine.post ('/api/documents', data = {'document': 'Detective Monk'})\n\n        r = engine.get ('/api/documents', params = {'q': 'detective'})\n        assert r.json () ['result'] == ['Detective Holmes', 'Detective Monk']\n```\n\n\n\n\n\n## Final Source Codes\nSee https://gitlab.com/skitai/atila/-/tree/master/examples/dumbseek\n\n\n\n\n\n\n\n\n\n## Conclusion\n- We add REST API to our existing source codes without any side effects\n- We can still use `dumbseek` as local library\n- Just add `skitaid.py`, we can serve as RESTful API online\n\n\n\n\n\n\n\n\n\n\n\n**[<< Back To README](https://gitlab.com/skitai/atila/-/blob/master/README.md)**\n\n\n\n\n\n\n\n\n\n\n\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Atila Framework",
    "version": "0.27.0",
    "project_urls": {
        "Download": "https://pypi.python.org/pypi/atila",
        "Homepage": "https://gitlab.com/sitai/atila"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "68a0f2802bada52d40e67badb667ea252f73a8f12d288c4b8b9e38aaaeba4832",
                "md5": "0888a2f46f0463f7c93bc3fc1c9517c6",
                "sha256": "a4501b8319aad835b752621176cc75669fe1fefa76d9b0657fdc4d4a6ac51c34"
            },
            "downloads": -1,
            "filename": "atila-0.27.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "0888a2f46f0463f7c93bc3fc1c9517c6",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 85716,
            "upload_time": "2024-04-13T10:36:24",
            "upload_time_iso_8601": "2024-04-13T10:36:24.806858Z",
            "url": "https://files.pythonhosted.org/packages/68/a0/f2802bada52d40e67badb667ea252f73a8f12d288c4b8b9e38aaaeba4832/atila-0.27.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-13 10:36:24",
    "github": false,
    "gitlab": true,
    "bitbucket": false,
    "codeberg": false,
    "gitlab_user": "sitai",
    "gitlab_project": "atila",
    "lcname": "atila"
}
        
Elapsed time: 0.23203s