Skip to content

Commit

Permalink
Merge pull request #1 from MissterHao/feature/backends
Browse files Browse the repository at this point in the history
SessionStore is added, also add dynamodb basic CRU functions
  • Loading branch information
MissterHao authored Jan 30, 2023
2 parents 3907f66 + 24bf727 commit 823ed73
Show file tree
Hide file tree
Showing 43 changed files with 1,487 additions and 2 deletions.
9 changes: 9 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[run]
omit =
tests/*.py
runtests.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
pragma: no cover
NotImplementedError
25 changes: 25 additions & 0 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CodeCov
on: [push, pull_request]
jobs:
run:
runs-on: ubuntu-latest
env:
OS: ubuntu-latest
PYTHON: '3.9'
steps:
- uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.8

- name: Generate Report
run: |
curl -Os https://uploader.codecov.io/latest/linux/codecov
chmod +x codecov
pip install coverage pytest
pip install -r requirements/coverage.txt
coverage run runtests.py
coverage report
./codecov
10 changes: 10 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"python.formatting.provider": "black",
"[python]": {
"editor.wordBasedSuggestions": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
}
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include LICENSE
include README*
recursive-include django-dysession *.py
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
all: coverage

.PHONY: lint
lint:
flake8

.PHONY: coverage
coverage:
coverage run runtests.py
coverage html

.PHONY: release
release:
python3 setup.py bdist

clean:
find . -type f -name *.pyc -delete
find . -type d -name __pycache__ -delete
44 changes: 42 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
# django-dysession
django-dysessino is a django extension by using AWS DynamoDB as a session backend
<div align="center">
<h1>django-dysession</h1>
<p>
django-dysessino is a django extension by using AWS DynamoDB as a session backend
</p>
<img src="https://socialify.git.ci/MissterHao/django-dysession/image?description=1&font=Source%20Code%20Pro&language=1&name=1&owner=1&pattern=Plus&stargazers=1&theme=Light" alt="django-dysession" width="640" height="320" />

<p align="center" >
<!-- First line -->
<a href="https://codecov.io/gh/MissterHao/django-dysession" style="margin-right: 8px;">
<img src="https://img.shields.io/codecov/c/gh/MissterHao/django-dysession?style=for-the-badge&token=M0U87NR9KI&logo=Codecov" alt="codecov" />
</a>
<a href="https://github.com/MissterHao/django-dysession">
<img
src="https://img.shields.io/pypi/pyversions/django-dysession?style=for-the-badge&logo=Python" alt="Supported Python version badge" >
</a>
<br>
<!-- Second line -->
<a href="https://github.com/MissterHao/django-dysession" style="margin-right: 8px;">
<img
src="https://img.shields.io/github/issues/MissterHao/django-dysession?style=for-the-badge" alt="Github Issue badge" />
</a>
<a href="https://github.com/MissterHao/django-dysession" style="margin-right: 8px;">
<img
src="https://img.shields.io/github/license/MissterHao/django-dysession?style=for-the-badge" alt="Lience badge" />
</a>
<a href="https://github.com/MissterHao/django-dysession" style="margin-right: 8px;">
<img
src="https://img.shields.io/pypi/dm/django-dysession?logo=python&style=for-the-badge" alt="Downloads badge" />
</a>
<br>
</p>
</div>


## What is a django-session?

## Requirements

## Installation

## Example
Empty file added codecov.yml
Empty file.
Empty file added dysession/__init__.py
Empty file.
1 change: 1 addition & 0 deletions dysession/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from django.contrib import admin
6 changes: 6 additions & 0 deletions dysession/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class DjangoDysessionConfig(AppConfig):
name = "dysession"
verbose_name = 'Django DynamoDB Session Backend'
Empty file added dysession/aws/__init__.py
Empty file.
164 changes: 164 additions & 0 deletions dysession/aws/dynamodb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from datetime import datetime
from typing import Any, Dict, Literal, Optional, Union

import boto3
from botocore import client as botoClitent
from django.utils import timezone

from dysession.aws.error import DynamodbItemNotFound, DynamodbTableNotFound
from dysession.backends.error import (SessionKeyDoesNotExist,
SessionKeyDuplicated)
from dysession.backends.model import SessionDataModel

from ..settings import get_config


def create_dynamodb_table(options: Dict[str, Union[str, int]], client=None) -> Dict:

if client is None:
client = boto3.client("dynamodb", region_name=get_config()["DYNAMODB_REGION"])

response = client.create_table(
AttributeDefinitions=[
{"AttributeName": options["pk"], "AttributeType": "S"},
# {"AttributeName": options["sk"], "AttributeType": "S"},
],
TableName=options["table"],
KeySchema=[
{"AttributeName": options["pk"], "KeyType": "HASH"},
# {"AttributeName": options["sk"], "KeyType": "RANGE"},
],
BillingMode="PAY_PER_REQUEST",
TableClass="STANDARD",
)

return response


def destory_dynamodb_table(options: Dict[str, Union[str, int]], client=None) -> Dict:

if client is None:
client = boto3.client("dynamodb", region_name=get_config()["DYNAMODB_REGION"])

response = client.delete_table(TableName=options["table"])
return response


def check_dynamodb_table_exists(table_name: Optional[str] = None, client=None) -> Dict:

if client is None:
client = boto3.client("dynamodb", region_name=get_config()["DYNAMODB_REGION"])

if table_name is None:
table_name = get_config()["DYNAMODB_TABLENAME"]

response = client.list_tables()
if table_name not in response["TableNames"]:
raise DynamodbTableNotFound

return response


def key_exists(session_key: str, table_name: Optional[str] = None, client=None) -> bool:

if client is None:
client = boto3.client("dynamodb", region_name=get_config()["DYNAMODB_REGION"])

if table_name is None:
table_name = get_config()["DYNAMODB_TABLENAME"]

assert type(session_key) is str, "session_key should be string type"

pk = get_config()["PARTITION_KEY_NAME"]

response = client.get_item(
TableName=table_name,
Key={
pk: {"S": session_key},
},
ProjectionExpression=f"{pk}",
)

return "Item" in response


def get_item(session_key: str, table_name: Optional[str] = None, client=None) -> bool:

if client is None:
client = boto3.client("dynamodb", region_name=get_config()["DYNAMODB_REGION"])

if table_name is None:
table_name = get_config()["DYNAMODB_TABLENAME"]

assert type(session_key) is str, "session_key should be string type"

pk = get_config()["PARTITION_KEY_NAME"]

response = client.get_item(
TableName=table_name,
Key={
pk: {"S": session_key},
},
)

if "Item" not in response:
raise DynamodbItemNotFound()

return response


def insert_session_item(
data: SessionDataModel,
table_name: Optional[str] = None,
return_consumed_capacity: Literal["INDEXES", "TOTAL", "NONE"] = "TOTAL",
) -> bool:
"""Insert a session key"""

assert type(data.session_key) is str, "session_key should be string type"

if table_name is None:
table_name = get_config()["DYNAMODB_TABLENAME"]

resource = boto3.resource("dynamodb", region_name=get_config()["DYNAMODB_REGION"])
table = resource.Table(table_name)
pk = get_config()["PARTITION_KEY_NAME"]

insert_item = {pk: data.session_key}
for key in data:
insert_item[key] = data[key]

response = table.put_item(
TableName=table_name,
Item=insert_item,
ReturnConsumedCapacity=return_consumed_capacity,
)

return response


class DynamoDB:
def __init__(self, client) -> None:
self.client = client

def get(
self, session_key: Optional[str] = None, ttl: Optional[datetime] = None
) -> Dict[str, Any]:
"""Return session data if dynamodb partision key is matched with inputed session_key"""
if session_key is None:
raise ValueError("session_key should be str type")


# if not found then raise
# raise SessionKeyDoesNotExist
# if key is expired
# raise SessionExpired

def set(self, session_key: Optional[str] = None, session_data=None) -> None:
return
# Partision key duplicated
raise SessionKeyDuplicated

def exists(self, session_key: Optional[str] = None) -> bool:
return False
# if not found then raise
raise SessionKeyDoesNotExist
6 changes: 6 additions & 0 deletions dysession/aws/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

class DynamodbTableNotFound(Exception):
pass

class DynamodbItemNotFound(Exception):
pass
Empty file added dysession/backends/__init__.py
Empty file.
Loading

0 comments on commit 823ed73

Please sign in to comment.