Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
622e41d
refactor: update the initial config
oandersonmagalhaes May 31, 2024
765a9ea
fix: circular dependencies
oandersonmagalhaes May 31, 2024
5cd1715
refact: changing SimplesConfig when SimplesAPI initialized
oandersonmagalhaes Jun 6, 2024
0f9db64
feat: updating auto_routing
oandersonmagalhaes Jun 8, 2024
1ab5bd9
Merge branch 'main' of https://github.com/oandersonmagalhaes/simplesa…
oandersonmagalhaes Jun 8, 2024
16aacc3
feature: adding internal logger
oandersonmagalhaes Jun 8, 2024
e0d299a
feature: expose the routes_path config
oandersonmagalhaes Jun 8, 2024
63fe652
feature: update auto_routing
oandersonmagalhaes Jun 8, 2024
42570eb
fix: fixing the auto_routing
oandersonmagalhaes Jun 8, 2024
764a14e
test: updating tests with new implementations
oandersonmagalhaes Jun 9, 2024
9741027
feature: adding simple example
oandersonmagalhaes Jun 9, 2024
d794e97
chore: update gitignore and add Makefile
oandersonmagalhaes Jun 9, 2024
067e526
feature: adding types
oandersonmagalhaes Jun 14, 2024
09570d2
fix: lifespan
oandersonmagalhaes Jun 14, 2024
d9edd73
feature: class SRequest
oandersonmagalhaes Jun 14, 2024
e05afab
feature: update initializing
oandersonmagalhaes Jun 14, 2024
2d15916
feature: update auto_routing to inject dependencies and params
oandersonmagalhaes Jun 14, 2024
f2d6f23
feature: remove unused
oandersonmagalhaes Jun 17, 2024
f54b7ad
feature: update logging
oandersonmagalhaes Jun 17, 2024
2f07b3a
feature: adding more dependencies
oandersonmagalhaes Jun 17, 2024
97ca42f
feature: update settings and env names
oandersonmagalhaes Jun 17, 2024
3f28dd6
feature: SRequest receive _simples_extra
oandersonmagalhaes Jun 17, 2024
962f97c
feature: configuring database
oandersonmagalhaes Jun 17, 2024
843c1c0
feature: configuring cache
oandersonmagalhaes Jun 17, 2024
9a31c1f
chore: adding docker-compose
oandersonmagalhaes Jun 17, 2024
728384a
chore: pgadmin servers auto-config
oandersonmagalhaes Jun 17, 2024
9143ece
fix: changing cache implementation
oandersonmagalhaes Jun 19, 2024
e576cf4
fix: auto_routing methods
oandersonmagalhaes Jun 19, 2024
ef05ff1
fix: database on livespan
oandersonmagalhaes Jun 19, 2024
05f65b9
fix: makefile
oandersonmagalhaes Jun 19, 2024
47df903
fix: consider the requestBody when is pydantic BaseModel
oandersonmagalhaes Jun 21, 2024
ace4e9b
feature: adding aws
oandersonmagalhaes Jun 22, 2024
922145f
refact: updating basic types
oandersonmagalhaes Jun 22, 2024
4ff815b
feature: adding aws implementations
oandersonmagalhaes Jun 22, 2024
662d2fc
chore: docker-compose configuring localstack aws
oandersonmagalhaes Jun 22, 2024
e1ae5e7
feature: configuring aws
oandersonmagalhaes Jun 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
poetry.lock
dist
dist
.vscode
*.pyc
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
publish:
ifndef version
$(error You must specify a version. Usage: make publish version=x.y.z)
endif
poetry version $(version)
poetry publish --build
63 changes: 63 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
services:
postgres:
image: postgres:13
container_name: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- "5432:5432"
volumes:
- ./docker_compose_volumes/postgres_data:/var/lib/postgresql/data

redis:
image: redis:6.2
container_name: redis
environment:
REDIS_PASSWORD: redis
ports:
- "6379:6379"
volumes:
- ./docker_compose_volumes/redis_data:/data

localstack:
image: localstack/localstack:latest
container_name: localstack
environment:
SERVICES: s3,sqs,sns,lambda,dynamodb,kinesis
PERSISTENCE: /tmp/localstack_data
ports:
- "4566:4566"
- "4568:4568"
- "4569:4569"
- "4571:4571"
- "4582:4582"
- "4599:4599"
- "4600:4600"
- "4601:4601"
volumes:
- ./docker_compose_volumes/localstack_init/init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh
- ./docker_compose_volumes/localstack_data:/tmp/localstack_data

pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@admin.com
PGADMIN_DEFAULT_PASSWORD: admin
PGADMIN_SERVER_JSON_FILE: /pgadmin4/config/servers.json
ports:
- "5050:80"
depends_on:
- postgres
volumes:
- ./docker_compose_volumes/pgadmin_data:/var/lib/pgadmin
- ./docker_compose_volumes/pgadmin_config:/pgadmin4/config
- ./docker_compose_volumes/pgadmin_config/servers.json:/pgadmin4/config/servers.json


volumes:
postgres_data:
redis_data:
localstack_data:
8 changes: 8 additions & 0 deletions docker_compose_volumes/localstack_init/init-aws.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
awslocal kinesis create-stream --stream-name samplestream --shard-count 1
awslocal sns create-topic --name sampletopic
awslocal sqs create-queue --queue-name samplequeue
awslocal sqs create-queue --queue-name samplequeueb
awslocal sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:sampletopic --protocol sqs --notification-endpoint arn:aws:sqs:us-east-1:000000000000:samplequeue
awslocal sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:sampletopic --protocol sqs --notification-endpoint arn:aws:sqs:us-east-1:000000000000:samplequeueb
awslocal s3api create-bucket --bucket samplebucket
16 changes: 16 additions & 0 deletions docker_compose_volumes/pgadmin_config/servers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Servers": {
"1": {
"Name": "PostgreSQL DockerCompose",
"Group": "Servers",
"Host": "postgres",
"Port": 5432,
"MaintenanceDB": "postgres",
"Username": "postgres",
"Password": "postgres",
"SSLMode": "prefer",
"Comment": "Pre-configured server."
}
}
}

3 changes: 3 additions & 0 deletions example/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from simplesapi import SimplesAPI

app = SimplesAPI(routes_path="example/routes")
2 changes: 2 additions & 0 deletions example/routes/health-check__GET.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
async def handler():
return {"message": "ok"}
2 changes: 2 additions & 0 deletions example/routes/users/[userId]__GET.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
async def handler(userId: str):
return {"userId":userId}
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
[tool.poetry]
name = "SimplesAPI"
version = "0.0.0-alpha.2"
version = "0.0.0-alpha.12"
description = "SimplesAPI is intended to be the easiest API to implementation in Python"
authors = ["Anderson Magalhaes <andersonmagalhaes.dev@gmail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.111.0"
asyncpg = "^0.29.0"
databases = "^0.9.0"
sqlalchemy = "^2.0.30"
redis = "^5.0.6"
aioboto3 = "^13.0.1"


[tool.poetry.group.dev.dependencies]
pytest = "^8.2.1"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
5 changes: 4 additions & 1 deletion simplesapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
SimplesAPI
"""

from .app import SimplesAPI as SimplesAPI
from .app import SimplesAPI as SimplesAPI, SimplesConfig as SimplesConfig

from .types import Database as Database, Cache as Cache

58 changes: 43 additions & 15 deletions simplesapi/app.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@

from typing import Optional
from typing import Any, Optional
from fastapi import FastAPI
from pydantic import BaseModel, Field

from simplesapi import lifespan, settings
from simplesapi.auto_routing import register_routes

class _Simples(BaseModel):
verbose: bool

from simplesapi.internal_logger import simplesapi_internal_logger

class SimplesConfig(BaseModel):
verbose: Optional[bool] = Field(default=False)
base_path: Optional[str] = Field(default="/")
redis_conn: Optional[str] = Field(default=None)
postgres_conn: Optional[str] = Field(default=None)
mysql_conn: Optional[str] = Field(default=None)
routes_path: Optional[str] = Field(default="routes")
cache_url: Optional[str] = Field(default=None)
cache_ssl: Optional[bool] = Field(default=None)
database_url: Optional[str] = Field(default=None)

aws_local: Optional[bool] = Field(default=False)
aws_access_key_id: Optional[str] = Field(default=None)
aws_secret_access_key: Optional[str] = Field(default=None)
aws_region_name: Optional[str] = Field(default=None)

class SimplesAPI(FastAPI):
def __init__(self, title="SimplesAPI", version="0.1.0", routes_path=None, base_path=None, redis_conn=None, postgres_conn=None, mysql_conn=None, verbose=False):
super().__init__(title=title, version=version)
self.simples = _Simples(verbose=verbose)
if routes_path:
register_routes(self,routes_path)

def validate_simples(self):
...
class SimplesAPI(FastAPI):
def __init__(
self, routes_path=None,
cache_url=settings.SIMPLESAPI_CACHE_URL,
cache_ssl=settings.SIMPLESAPI_CACHE_SSL,
database_url=settings.SIMPLES_DABASE_URL,
aws_access_key_id=settings.SIMPLES_AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.SIMPLES_AWS_SECRET_ACCESS_KEY,
aws_region_name=settings.SIMPLES_AWS_REGION_NAME,
aws_local=settings.SIMPLES_AWS_LOCAL,
*args, **kwargs
):
simplesapi_internal_logger()
self.simples = SimplesConfig(
routes_path=routes_path,
cache_url=cache_url,
cache_ssl=cache_ssl,
database_url=database_url,
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
aws_region_name=aws_region_name,
aws_local=aws_local,
**kwargs
)
super().__init__(lifespan=lifespan.lifespan, *args, **kwargs)
if self.simples.routes_path:
register_routes(self, self.simples.routes_path)
56 changes: 33 additions & 23 deletions simplesapi/auto_routing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

import importlib
import logging
import os
from fastapi import FastAPI
from simplesapi.request import SRequest


logger = logging.getLogger("SimplesAPI")


def _import_handler(module_path, handler_name="handler"):
Expand All @@ -9,32 +14,37 @@ def _import_handler(module_path, handler_name="handler"):
spec.loader.exec_module(module)
return getattr(module, handler_name)

def _create_route_from_file(app, file_path):

def _create_route_from_file(app: FastAPI, file_path: str, base_path: str) -> None:
parts = file_path.split(os.sep)
method_file = parts[-1]
method = method_file.split('.')[0].split('__')[1].lower() if '__' in method_file else method_file.split('.')[0].lower()

method_file_result = parts[-1].split(".")[0].split("__")

last_route = method_file_result[0] if len(method_file_result) > 1 else ""
last_route = last_route.replace("[", "{").replace("]", "}")
method = method_file_result[-1].upper()

route = os.sep.join(parts[:-1])
route = route.replace('routes', '')
route = route.replace('[', '{').replace(']', '}')
route = '/' + route.strip(os.sep)
route = route.replace(base_path, "", 1)
route = route.replace("[", "{").replace("]", "}")
route = (
"/" + route.strip(os.sep).replace(os.sep, "/") + "/" if len(route) > 0 else "/"
)

if last_route:
route += last_route

handler = _import_handler(file_path)

if method == "get":
app.get(route)(handler)
elif method == "post":
app.post(route)(handler)
elif method == "put":
app.put(route)(handler)
elif method == "delete":
app.delete(route)(handler)
elif method == "patch":
app.patch(route)(handler)

def register_routes(app, base_path):
s_handler = SRequest(route=route, handler=handler, method=method)
available_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"]
if method in available_methods:
app.add_api_route(route, s_handler.request, methods=[method])
logger.info(f"Added route {method.upper()} {route}")


def register_routes(app, base_path: str):
base_path = base_path.replace("/", os.sep)
for root, _, files in os.walk(base_path):
for file in files:
if file.endswith('.py'):
if file.endswith(".py"):
file_path = os.path.join(root, file)
_create_route_from_file(app, file_path)
_create_route_from_file(app, file_path, base_path)
4 changes: 4 additions & 0 deletions simplesapi/aws/types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .kinesis import Kinesis as Kinesis
from .s3 import S3 as S3
from .sns import SNS as SNS
from .sqs import SQS as SQS
32 changes: 32 additions & 0 deletions simplesapi/aws/types/kinesis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from simplesapi.types import AWSSession
from typing import Dict, Any

class Kinesis:
def __init__(self, session: AWSSession, stream_name: str):
self.stream_name = stream_name
self.session = session
self.client_config = {"service_name":"kinesis", "endpoint_url":"http://localhost:4566"} if session.aws_local else {"service_name":"kinesis"}

async def put_record(self, data: Dict[str, Any], partition_key: str):
async with self.session.client(**self.client_config) as client:
response = await client.put_record(
StreamName=self.stream_name,
Data=data,
PartitionKey=partition_key
)
return response

async def put_records(self, records: Dict[str, Any]):
async with self.session.client(**self.client_config) as client:
response = await client.put_records(
StreamName=self.stream_name,
Records=records
)
return response

async def describe_stream(self):
async with self.session.client(**self.client_config) as client:
response = await client.describe_stream(
StreamName=self.stream_name
)
return response
48 changes: 48 additions & 0 deletions simplesapi/aws/types/s3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from simplesapi.types import AWSSession

class S3:
def __init__(self, session: AWSSession, bucket_name: str):
self.bucket_name = bucket_name
self.session = session
self.client_config = {"service_name":"s3", "endpoint_url":"http://localhost:4566"} if session.aws_local else {"service_name":"s3"}

async def upload_file(self, file_path: str, object_key: str):
async with self.session.client(**self.client_config) as client:
response = await client.upload_file(
file_path,
self.bucket_name,
object_key
)
return response

async def download_file(self, object_key: str, file_path: str):
async with self.session.client(**self.client_config) as client:
response = await client.download_file(
self.bucket_name,
object_key,
file_path
)
return response

async def delete_object(self, object_key: str):
async with self.session.client(**self.client_config) as client:
response = await client.delete_object(
Bucket=self.bucket_name,
Key=object_key
)
return response

async def list_objects(self):
async with self.session.client(**self.client_config) as client:
response = await client.list_objects(
Bucket=self.bucket_name
)
return response

async def get_object(self, object_key: str):
async with self.session.client('s3') as client:
response = await client.get_object(
Bucket=self.bucket_name,
Key=object_key
)
return response
Loading