Metadata-Version: 2.1
Name: sanic-cookies
Version: 0.4.3
Summary: Cookies and Session Management for Sanic
Home-page: https://github.com/omarryhan/sanic-cookies
Author: Omar Ryhan
Author-email: omarryhan@gmail.com
License: GNU
Description: <p align="center">
          <img src="https://upload.wikimedia.org/wikipedia/commons/7/70/Cookie.png" alt="Logo" width="250" height="250"/>
          <p align="center">
            <a href="https://github.com/omarryhan/sanic-cookies"><img alt="Software License" src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square" /></a>
            <a href="https://travis-ci.org/omarryhan/sanic-cookies"><img alt="Build Status" src="https://travis-ci.org/omarryhan/sanic-cookies.svg?branch=master" /></a>
            <a href="https://github.com/python/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg" /></a>
            <a href="https://pepy.tech/badge/sanic-cookies"><img alt="Downloads" src="https://pepy.tech/badge/sanic-cookies" /></a>
            <a href="https://pepy.tech/badge/sanic-cookies/month"><img alt="Monthly Downloads" src="https://pepy.tech/badge/sanic-cookies/month" /></a>
          </p>
        </p>
        
        # Sanic Cookies
        
        Much of the code here is borrowed from [sanic_session](https://github.com/xen/sanic_session).
        
        I wanted to make some changes that would break a big part of `sanic_session`'s API, so I decided to create this repo instead.
        
        Sanic cookies supports both client side and server side cookies.
        
        ## Main deviations from sanic_session are
        
        - **Interfaces are only responsible for reading/writing the `SessionDict`:**
        
          Session management logic is handled by the `Session` object
        
        - **No race conditions**:
        
          *By using:*
        
            ```python 3.7
            async with request['session']:
                request['session']['foo'] = 'bar'
            ```
        
          *instead of:*
        
            ```python 3.7
            request['session']['foo'] = 'bar'
            ```
        
          It is still however possible to use the `session_dict` without a context manager, but it will raise some warnings,
          unless it's explicitly turned off (warn_lock=False)
        
          **Note:**
        
            The locking mechanism used here only keeps track of locks on a thread-level, which means, an application that is horizontally scaled or one that runs on more than one process won't fully benefit from the locking mechanism that sanic-cookies currently has in place and might encounter some race conditions.
            I have plans to introduce a distributed locking mechanism. Probably using something like: [Aioredlock](https://github.com/joanvila/aioredlock).
            But for now, you should know that the locking mechanism that is currently in place will not work in a multi-process environment.
        
        - **A simpler implementation of SessionDict that helps me sleep in peace at night. (Probably less performant)**
        
        - **In memory interface schedules cleanup to avoid running out of memory**
        
        - **Encrypted client side cookie interface**
        
        - **Ability to add more than one interface to the same session**
        
        - **Authenticated Session implementation**
        
        ## Setup ⚙️
        
        ```bash
        $ pip install sanic_cookies
        ```
        
        ## Quick Start
        
        ```python 3.7
        from sanic_cookies import Session, InMemory
        from sanic import Sanic
        
        app = Sanic()
        Session(app, master_interface=InMemory())
        
        @app.route('/')
        async def handler(request):
            async with request['session'] as sess:
                sess['foo'] = 'bar'
        ```
        
        ## Usage
        
        ### Running multiple interfaces
        
        ```python 3.7
        from sanic_cookies import Session, InMemory, Aioredis
        from sanic import Sanic
        
        inmem = InMemory()
        aioredis = AioRedis(aioredis_pool_instance)
        app = Sanic()
        sess = Session(app, master_interface=inmem, session_name='my_1st_sess')
        sess.add_interface(aioredis)
        
        @app.route('/')
        async def index(request):
            async with request['my_1st_session'] as sess:
                sess['foo'] = 'bar'
                # At this point 'foo' = 'bar' is written both to the inmemory
                # interface and the aioredis interface
        
            async with request['my_1st_session'] as sess:
                # When reading, your session will always read from the "master_interface"
                # In that case it's the inmem interface
                assert sess['foo'] == 'bar'
            # Such pattern can be useful in many cases 
            # e.g. you want to share your session information with an analytics team
        ```
        
        ### Running multiple sessions
        
        ```python 3.7
        from sanic_cookies import Session, AuthSession, InMemory, InCookieEncrypted, AioRedis
        from sanic import Sanic
        
        inmem = InMemory()
        aioredis = Aioredis(aioredis_pool_instance)
        incookie = InCookieEncrypted(b'fernetsecretkey')
        
        app = Sanic()
        
        incookie_session = Session(
            app,
            master_interface=incookie,
            session_name='incookiesess',
            cookie_name='INCOOKIE'
        )
        
        generic_session = Session(
            app,
            master_interface=inmem,
            session_name='session',
            cookie_name='SESSION'
        )
        
        auth_session = AuthSession(
            app,
            master_interface=aioredis,
            session_name='auth_session',
            cookie_name='SECURE_SESSION'
        )
        
        # for production (HTTPs) set `secure=True` in your auth_session,
        # but this will fail in local development
        
        @app.route('/')
        async def index(request):
            async with request['incookie_session'] as sess:
                sess['foo'] = 'bar'
        
            async with request['session'] as sess:
                sess['bar'] = 'baz'
        
            async with request['auth_session'] as sess:
                sess['baz'] = 'foo'
        ```
        
        ### AuthSession
        
        Following up on the previous example:
        
        ```python 3.7
        from sanic_cookies import login_required
        
        @app.route('/login')
        async def login(request):
            # 1. User verification logic
        
            # both will work (Whatever is json serializble will)
            # If you want to pickle an object simply change the default
            # encoder&decoder in the interfaces plugged in to your AuthSession
            authorized_user = 123 
            authorized_user = {'user_id': 123, 'email': 'foo@bar.baz'}
        
            # 2. Login user
        
            # Here we access the session object
            # (not the session dict that is accessible from the request) from the app
            await request.app.exts.auth_session.login_user(request, authorized_user)
        
            # 3. Use the session dict safely and exclusively for the logged in user
        
            async with request['auth_session'] as sess:
                sess['foo'] = 'bar'
                current_user = sess['current_user']
            assert current_user == await request.app.exts.auth_session.current_user()
        
        @app.route('/logout')
        async def logout(request):
            async with request['auth_session'] as sess:
                assert sess['foo'] == 'bar'  # From before
        
            await request.app.exts.auth_session.logout_user(request)  # Resets the session
        
            async with request['auth_session'] as sess:
                assert sess.get('foo') is None  # should never fail
                assert sess.get('current_user') is None  # should never fail
        
        @app.route('/protected')
        @login_required()
        async def protected(request):
            assert await request.app.exts.auth_session.current_user() is not None  # should never fail
        ```
        
        ## Interfaces available
        
        1. In memory
        
            ``` python 3.7
            from sanic_cookies import Session, InMemory
            from sanic import Sanic
        
            interface = InMemory()
            app = Sanic()
            Session(app, master_interface=interface)
        
            # You can skip this part if you don't want scheduled interface cleanup
            @app.listener('before_server_start')
            def init_inmemory(app, loop):
                interface.init()
            @app.listener('after_server_stop')
            def kill_inmemory(app, loop):
                interface.kill()
        
            @app.route('/')
            async def handler(request):
                async with request['session'] as sess:
                    sess['foo'] = 'bar'
            ```
        
        2. Aioredis
        
            ```python 3.7
            from aioredis import Aioredis
            from sanic_cookies import Aioredis as AioredisInterface
            from sanic import Sanic
        
            app = Sanic()
            aioredis_pool_instance = Aioredis()
            aioredis = AioredisInterface(aioredis_pool_instance)
            Session(app, master_interface=interface)
        
            @app.route('/')
            async def handler(request):
                async with request['session'] as sess:
                    sess['foo'] = 'bar'
            ```
        
        3. Encrypted in-cookie (using the amazing cryptography.Fernet library)
        
            i. Open a Python terminal and generate a new Fernet key:
        
            ```python 3.7
            >>> from cryptography.fernet import Fernet
        
            >>> SESSION_KEY = Fernet.generate_key()
        
            >>> print(SESSION_KEY)
        
            b'copy me to your sanic app and keep me really secure'
            ```
        
            ii. Write your app
        
            ```python 3.7
            from sanic import Sanic
            from sanic_cookies import Session, InCookieEncrypted
        
            app = Sanic()
            app.config.SESSION_KEY = SESSION_KEY
        
            Session(
                app,
                master_interface=InCookieEncrypted(app.config.SESSION_KEY),
            )
        
            @app.route('/')
            async def handler(request):
                async with request['session'] as sess:
                    sess['foo'] = 'bar'
            ```
        
        4. Gino-AsyncPG (Postgres 9.5+):
        
            i. Manually create a table:
        
            ```sql
            CREATE TABLE IF NOT EXISTS sessions
            (
                created_at timestamp without time zone NOT NULL,
                expires_at timestamp without time zone,
                sid character varying,
                val character varying,
                CONSTRAINT sessions_pkey PRIMARY KEY (sid)
            );
            ```
        
            ii. Add the interface:
        
            ```python 3.7
            from sanic import Sanic
            from gino.ext.sanic import Gino
            from sanic_cookies import GinoAsyncPG
        
            from something_secure import DB_SETTINGS
        
            app = Sanic()
            app.config.update(DB_SETTINGS)
            db = Gino()
            db.init_app(app)
        
            interface = GinoAsyncPG(client=db)
            auth_session = AuthSession(app, master_interface=interface)
        
            if __name__ == '__main__':
                app.run(host='127.0.0.1', port='8080')
            ```
        
        ## Sessions available
        
        1. Session (A generic session interface)
        2. AuthSession (A session interface with login_user, logout_user, current_user logic)
        
        ## Other pluggable parts
        
        1. Encoders and Decoders (Default to ujson)
        2. SID factory (Default to uuid.uuid4)
        3. Session dict implementation
        
        ## Contact 📧
        
        I currently work as a freelance software devloper. Like my work and got a gig for me?
        
        Want to hire me fulltime? Send me an email @ omarryhan@gmail.com
        
        ## Buy me a coffee ☕
        
        **Bitcoin:** 3NmywNKr1Lzo8gyNXFUnzvboziACpEa31z
        
        **Ethereum:** 0x1E1400C31Cd813685FE0f6D29E0F91c1Da4675aE
        
        **Bitcoin Cash:** qqzn7rsav6hr3zqcp4829s48hvsvjat4zq7j42wkxd
        
        **Litecoin:** MB5M3cE3jE4E8NwGCWoFjLvGqjDqPyyEJp
        
        **Paypal:** https://paypal.me/omarryhan
        
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3.7
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown
