Skip to content

Commit

Permalink
Merge pull request #329 from napse-invest/dev
Browse files Browse the repository at this point in the history
Release 12/03/2024 (Initial launch)
  • Loading branch information
tomjeannesson authored Mar 12, 2024
2 parents 0fec978 + e8076bc commit 9fc518d
Show file tree
Hide file tree
Showing 129 changed files with 2,101 additions and 793 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,5 @@ cython_debug/
secrets.json
full-requirements.txt
.ruff_cache/
*.sqlite3
*.sqlite3
dump.rdb
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.2.2
hooks:
# Run the linter.
- id: ruff
entry: ruff check --force-exclude --config .github/pyproject.toml
# Run the formatter.
- id: ruff-format
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ clean:
rm tests/test_app/db.sqlite3

celery:
source .venv/bin/activate && watchfiles --filter python celery.__main__.main --args "-A tests.test_app worker --beat -l INFO"
source .venv/bin/activate && watchfiles --filter python celery.__main__.main --args "-A tests.test_app worker --beat -l INFO" --verbosity info

shell:
source .venv/bin/activate && python tests/test_app/manage.py shell
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ Or you can use it as a local backend for the [Napse desktop application](https:/

Find more details for installation in the [documentation](https://napse-invest.github.io/django-napse/#installation).

### Quick start

```shell
make setup
make up
```

### Documentation

You can find the documentation [here](https://napse-invest.github.io/django-napse/).
Expand Down
21 changes: 10 additions & 11 deletions django_napse/api/api_urls.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import re
from importlib import import_module
from pathlib import Path
from types import ModuleType
from typing import List
from typing import TYPE_CHECKING, List

from rest_framework.routers import DefaultRouter
from rest_framework.viewsets import GenericViewSet

if TYPE_CHECKING:
from types import ModuleType

class ConflictingUrlNames(Exception):
pass

class ConflictingUrlNamesError(Exception):
"""When several viewset have the same automatic url."""


def build_main_router() -> DefaultRouter:
Expand All @@ -24,12 +26,11 @@ def build_main_router() -> DefaultRouter:
api_dir = Path(__file__).parent
api_modules_folders_names = [folder.name for folder in api_dir.iterdir() if folder.is_dir() and not folder.name.startswith("_")]
for module_name in api_modules_folders_names:
# print(f"module name: {module_name}")
try:
module: ModuleType = import_module(f"django_napse.api.{module_name}.views")
except (ImportError, ModuleNotFoundError) as error: # noqa: F841
# print(f"Could not import module {module_name} ({type(error)})")
# print(error)
except (ImportError, ModuleNotFoundError) as error:
print(f"Could not import module {module_name} ({type(error)})")
print(error)
continue
for obj in vars(module).values():
if isinstance(obj, type) and issubclass(obj, GenericViewSet):
Expand All @@ -38,13 +39,11 @@ def build_main_router() -> DefaultRouter:

if url_name in url_name_list:
error_msg: str = f"Url name {url_name} already exists"
raise ConflictingUrlNames(error_msg)
raise ConflictingUrlNamesError(error_msg)
main_router.register(url_name, obj, basename=url_name)
url_name_list.append(url_name)

return main_router


main_api_router = build_main_router()
# for url in main_api_router.urls:
# print(url)
8 changes: 6 additions & 2 deletions django_napse/api/bots/serializers/architecture_serializer.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from typing import ClassVar

from rest_framework import serializers

from django_napse.core.models.bots.architecture import Architecture


class ArchitectureSerializer(serializers.ModelSerializer):
class Meta:
"""Serialize an Architecture instance."""

class Meta: # noqa: D106
model = Architecture
fields = "__all__"
read_only_fields = [
read_only_fields: ClassVar[list[str]] = [
"id",
]
60 changes: 32 additions & 28 deletions django_napse/api/bots/serializers/bot_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
from uuid import UUID

from rest_framework import serializers
from rest_framework.fields import empty

from django_napse.api.orders.serializers import OrderSerializer
from django_napse.api.wallets.serializers import WalletSerializer
from django_napse.core.models import Bot, BotHistory, ConnectionWallet, NapseSpace, Order
from django_napse.core.models import Bot, BotHistory, ConnectionWallet, Order, Space


class BotSerializer(serializers.ModelSerializer):
"""Serialize bot instances."""

delta = serializers.SerializerMethodField(read_only=True)
space = serializers.SerializerMethodField(read_only=True)
value = serializers.SerializerMethodField(read_only=True)
exchange_account = serializers.SerializerMethodField(read_only=True)
fleet = serializers.CharField(source="fleet.uuid", read_only=True)

Expand All @@ -35,16 +35,10 @@ class Meta: # noqa: D106
"space",
]

def __init__(
self,
instance: Bot = None,
data: dict[str, any] = empty,
space: NapseSpace = None,
**kwargs: dict[str, any],
) -> None:
def __init__(self, *args: list[any], space: Space | None = None, **kwargs: dict[str, any]) -> None:
"""Add space to the serializer and run the default constructor."""
self.space = space
super().__init__(instance=instance, data=data, **kwargs)
super().__init__(*args, **kwargs)

def get_delta(self, instance: Bot) -> float:
"""Delta on the last 30 days."""
Expand All @@ -67,12 +61,22 @@ def get_exchange_account(self, instance: Bot) -> UUID | None:
return None
return instance.exchange_account.uuid

def get_value(self, instance: Bot) -> float:
"""Return bot's value."""
return instance.value(space=self.space)

def create(self, validated_data: dict[str, any]) -> Bot: # noqa: ARG002
"""Create a bot instance."""
error_msg: str = "You don't have to create a bot from an endpoint"
raise ValueError(error_msg)


class BotDetailSerializer(serializers.ModelSerializer):
"""Deep dive in bot's data for serialization."""

delta = serializers.SerializerMethodField(read_only=True)
space = serializers.SerializerMethodField(read_only=True)
value = serializers.SerializerMethodField(read_only=True)
exchange_account = serializers.CharField(source="exchange_account.uuid", read_only=True)
fleet = serializers.CharField(source="fleet.uuid", read_only=True)

Expand All @@ -82,7 +86,7 @@ class BotDetailSerializer(serializers.ModelSerializer):

class Meta: # noqa: D106
model = Bot
fields: ClassVar = [
fields: ClassVar[list[str]] = [
"name",
"uuid",
"value",
Expand All @@ -94,26 +98,12 @@ class Meta: # noqa: D106
"wallet",
"orders",
]
read_only_fields: ClassVar = [
"uuid",
"value",
"delta",
"statistics",
"space",
"wallet",
"orders",
]
read_only_fields: ClassVar = fields

def __init__(
self,
instance: Bot = None,
data: dict[str, any] = empty,
space: NapseSpace = None,
**kwargs: dict[str, any],
) -> None:
def __init__(self, *args: list[any], space: Space | None = None, **kwargs: dict[str, any]) -> None:
"""Add space to the serializer and run the default constructor."""
self.space = space
super().__init__(instance=instance, data=data, **kwargs)
super().__init__(*args, **kwargs)

def get_delta(self, instance: Bot) -> float:
"""Delta on the last 30 days."""
Expand All @@ -123,6 +113,10 @@ def get_delta(self, instance: Bot) -> float:
return 0
return history.get_delta()

def get_value(self, instance: Bot) -> float:
"""Return bot's value."""
return instance.value(space=self.space)

def get_space(self, instance: Bot) -> UUID | None: # noqa: ARG002
"""Return the space used for the space containerization."""
if self.space is None:
Expand Down Expand Up @@ -160,3 +154,13 @@ def get_orders(self, instance: Bot) -> list[dict[str, any]]:
),
many=True,
).data

def create(self, validated_data: dict[str, any]) -> Bot: # noqa: ARG002
"""Create a bot instance."""
error_msg: str = "You don't have to create a bot from an endpoint"
raise ValueError(error_msg)

def update(self, instance: Bot, validated_data: dict[str, any]) -> Bot: # noqa: ARG002
"""Update a bot instance."""
error_msg: str = "You don't have to update a bot from an endpoint"
raise ValueError(error_msg)
8 changes: 6 additions & 2 deletions django_napse/api/bots/serializers/config_serializer.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from typing import ClassVar

from rest_framework import serializers

from django_napse.core.models.bots.config import BotConfig


class ConfigSerializer(serializers.ModelSerializer):
class Meta:
"""Serialize a BotConfig instance."""

class Meta: # noqa: D106
model = BotConfig
fields = "__all__"
read_only_fields = [
read_only_fields: ClassVar[list[str]] = [
"uuid",
"immutable",
]
8 changes: 6 additions & 2 deletions django_napse/api/bots/serializers/plugin_serializer.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
from typing import ClassVar

from rest_framework import serializers

from django_napse.api.bots.serializers.strategy_serializer import StrategySerializer
from django_napse.core.models.bots.plugin import Plugin


class PluginSerializer(serializers.ModelSerializer):
"""Serialize a Plugin instance."""

strategy = StrategySerializer()

class Meta:
class Meta: # noqa: D106
model = Plugin
fields = "__all__"
read_only_fields = [
read_only_fields: ClassVar[list[str]] = [
"id",
]
4 changes: 3 additions & 1 deletion django_napse/api/bots/serializers/strategy_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@


class StrategySerializer(serializers.ModelSerializer):
"""Serialize a Strategy instance."""

config = ConfigSerializer()
architecture = ArchitectureSerializer()

class Meta:
class Meta: # noqa: D106
model = Strategy
fields = "__all__"
4 changes: 2 additions & 2 deletions django_napse/api/bots/views/bot_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django_napse.api.bots.serializers.bot_serializers import BotDetailSerializer, BotSerializer
from django_napse.api.custom_permissions import HasSpace
from django_napse.api.custom_viewset import CustomViewSet
from django_napse.core.models import Bot, NapseSpace
from django_napse.core.models import Bot, Space
from django_napse.utils.errors import APIError


Expand All @@ -28,7 +28,7 @@ def get_queryset(self) -> list[QuerySet[Bot]] | dict[str, QuerySet[Bot]]:
ValueError: Space not found.
"""
api_key = self.get_api_key(self.request)
spaces = NapseSpace.objects.all() if api_key.is_master_key else [permission.space for permission in api_key.permissions.all()]
spaces = Space.objects.all() if api_key.is_master_key else [permission.space for permission in api_key.permissions.all()]

# Free bots across all available spaces
if self.request.query_params.get("free", False):
Expand Down
1 change: 0 additions & 1 deletion django_napse/api/connections/__init__.py

This file was deleted.

6 changes: 3 additions & 3 deletions django_napse/api/custom_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework.permissions import BasePermission
from rest_framework_api_key.permissions import HasAPIKey # noqa

from django_napse.core.models import NapseSpace
from django_napse.core.models import Space
from django_napse.utils.constants import PERMISSION_TYPES
from django_napse.utils.errors import APIError

Expand All @@ -11,8 +11,8 @@ def check_for_space(request):
if "space" not in request.query_params:
raise APIError.MissingSpace()
try:
return NapseSpace.objects.get(uuid=request.query_params["space"])
except NapseSpace.DoesNotExist as e:
return Space.objects.get(uuid=request.query_params["space"])
except Space.DoesNotExist as e:
raise APIError.InvalidSpace() from e
except ValidationError as e:
raise APIError.InvalidSpace() from e
Expand Down
25 changes: 18 additions & 7 deletions django_napse/api/custom_viewset.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from rest_framework.viewsets import GenericViewSet

from django_napse.auth.models import NapseAPIKey
from django_napse.core.models import NapseSpace
from django_napse.core.models import Space
from django_napse.utils.errors import APIError

if TYPE_CHECKING:
from rest_framework.request import Request


class CustomViewSet(GenericViewSet):
def get_api_key(self, request):
"""Base of all ViewSets."""

def get_api_key(self, request: Request) -> NapseAPIKey:
"""Return the api key from the request."""
try:
return NapseAPIKey.objects.get_from_key(request.META["HTTP_AUTHORIZATION"].split()[1])
except NapseAPIKey.DoesNotExist as e:
raise APIError.InvalidAPIKey() from e
raise APIError.InvalidAPIKey from e
except KeyError as e:
raise APIError.NoAPIKey() from e
raise APIError.NoAPIKey from e

def get_space(self, request) -> NapseSpace | None:
def get_space(self, request: Request) -> Space | None:
"""Return the space from the request."""
try:
return NapseSpace.objects.get(uuid=request.query_params["space"])
except NapseSpace.DoesNotExist:
return Space.objects.get(uuid=request.query_params["space"])
except Space.DoesNotExist:
return None
Loading

0 comments on commit 9fc518d

Please sign in to comment.