diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml
index f4c1c65c..fc100236 100644
--- a/.github/workflows/django.yml
+++ b/.github/workflows/django.yml
@@ -95,8 +95,8 @@ jobs:
label: Coverage
message: ${{ env.COVERAGE }} %
valColorRange: ${{ env.COVERAGE }}
- minColorRange: 50
- maxColorRange: 95
+ minColorRange: 70
+ maxColorRange: 100
stop-runner:
name: Stop self-hosted EC2 runner
diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml
index 8de396c7..a283fb59 100644
--- a/.github/workflows/mkdocs.yml
+++ b/.github/workflows/mkdocs.yml
@@ -9,40 +9,46 @@ permissions:
contents: write
jobs:
- api-schema:
- name: API schema
- runs-on: ubuntu-latest
+ start-runner:
+ name: Start self-hosted EC2 runner
+ runs-on: ubuntu-latest
+ outputs:
+ label: ${{ steps.start-ec2-runner.outputs.label }}
+ ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }}
steps:
- - uses: actions/checkout@v4
- with:
- python-version: 3.11
-
- - name: Set up python
- uses: actions/setup-python@v4
- with:
- python-version: "3.11"
- architecture: "x64"
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install pip-tools
- pip-compile ./requirements/development.txt --output-file ./full-requirements.txt --resolver=backtracking
- pip install -r ./full-requirements.txt
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ - name: Start EC2 runner
+ id: start-ec2-runner
+ uses: machulav/ec2-github-runner@v2
+ with:
+ mode: start
+ github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
+ ec2-image-id: ${{ secrets.AWS_EC2_IMAGE_ID }}
+ ec2-instance-type: t3.micro
+ subnet-id: ${{ secrets.AWS_SUBNET_ID }}
+ security-group-id: ${{ secrets.AWS_SECURITY_GROUP_ID }}
- - name: Write open-api schema
- run: python tests/test_app/manage.py spectacular --file docs/schema.yml
+
deploy:
- needs: api-schema
+ needs: start-runner
name: Deploy documentation
- runs-on: ubuntu-latest
+ runs-on: ${{ needs.start-runner.outputs.label }}
+ timeout-minutes: 10
+ environment: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }}
steps:
- uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: actions/setup-python@v4
with:
- python-version: 3.x
+ python-version: 3.11
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v3
with:
@@ -50,6 +56,45 @@ jobs:
path: .cache
restore-keys: |
mkdocs-material-
- - run: |
- pip install -r requirements/development.txt
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install pip-tools
+ pip-compile ./requirements/development.txt --output-file ./full-requirements.txt --resolver=backtracking
+ pip install -r ./full-requirements.txt
+ - name: Tests with coverage
+ run: |
+ export PYTHONPATH="$PYTHONPATH:./django-napse/"
+ export NAPSE_IS_IN_PIPELINE=True
+ cd tests/test_app
+ bash setup_secrets.sh
+ cd ../..
+ python3 tests/test_app/manage.py makemigrations && python3 tests/test_app/manage.py migrate
+ coverage run ./tests/test_app/manage.py test -v2 --keepdb && coverage html
+ - name: Write open-api schema
+ run: python tests/test_app/manage.py spectacular --file docs/schema.yml
+ - name: Deploy documentation
+ run: |
mkdocs gh-deploy --force
+
+ stop-runner:
+ name: Stop self-hosted EC2 runner
+ needs:
+ - start-runner # required to get output from the start-runner job
+ - deploy # required to wait when the main job is done
+ runs-on: ubuntu-latest
+ if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs
+ steps:
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: ${{ secrets.AWS_REGION }}
+ - name: Stop EC2 runner
+ uses: machulav/ec2-github-runner@v2
+ with:
+ mode: stop
+ github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
+ label: ${{ needs.start-runner.outputs.label }}
+ ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 277af169..44d25284 100644
--- a/Makefile
+++ b/Makefile
@@ -6,11 +6,11 @@ all: setup-testing-environment makemigrations migrate runserver
setup:
ifeq ($(OS),Darwin) # Mac OS X
- ./setup-osx.sh
+ ./setup/setup-osx.sh
else ifeq ($(OS),Linux)
- ./setup-unix.sh
+ ./setup/setup-unix.sh
else
- ./setup-windows.sh
+ ./setup/setup-windows.sh
endif
setup-testing-environment:
diff --git a/README.md b/README.md
index eb2abb2e..61cc3e3b 100644
--- a/README.md
+++ b/README.md
@@ -22,47 +22,29 @@
Django Napse ยท
- Usefull commands .
+ Documentation .
How to contribute
## django-napse
-....
-## Useful commands
-Unless otherwise specified, all commands are to be run at the root folder of the project.
+Django napse is a portfolio management module using trading bots.
+This directory can be used as a django module, which you can install as follows:
-### Create a new project
-- Unix \
-```source setup-unix.sh```
-
-- Windows \
-```.\setup-windows.ps1```
+```bash
+pip install django-napse
+```
-### Run a test version of the project
+Or you can use it as a local backend for the [Napse desktop application](https://github.com/napse-invest/Napse), by cloning the repository.
-- Build migrations \
-```make makemigrations```
-- Apply migrations \
-```make migrate```
-- Run server \
-```make runserver```
+Find more details for installation in the [documentation](https://napse-invest.github.io/django-napse/#installation).
-### Run coverage tests
+### Documentation
-- Run tests \
-```test-napse```
-- Run tests with coverage \
-```coverage```
-- Run tests with coverage and open coverage report \
-```coverage-open```
+You can find the documentation [here](https://napse-invest.github.io/django-napse/).
-## Documentation
-[Docs](https://napse-invest.github.io/django-napse/)
+## How to contribute
-Run mkdocs server:
-```
-make mkdocs
-```
\ No newline at end of file
+If you want to contribute to the project, please read the [contributing guidelines](https://napse-invest.github.io/django-napse/contributing/) first. You will find the setup instructions and our standards.
\ No newline at end of file
diff --git a/django_napse/api/fleets/serializers/__init__.py b/django_napse/api/fleets/serializers/__init__.py
index b18113f7..deafb8a4 100644
--- a/django_napse/api/fleets/serializers/__init__.py
+++ b/django_napse/api/fleets/serializers/__init__.py
@@ -1 +1,2 @@
+from .cluster_serialisers import ClusterSerializer
from .fleet_serializers import FleetDetailSerializer, FleetSerializer
diff --git a/django_napse/api/fleets/serializers/cluster_serialisers.py b/django_napse/api/fleets/serializers/cluster_serialisers.py
new file mode 100644
index 00000000..2508c7c1
--- /dev/null
+++ b/django_napse/api/fleets/serializers/cluster_serialisers.py
@@ -0,0 +1,17 @@
+from rest_framework import serializers
+
+from django_napse.api.bots.serializers import BotSerializer
+from django_napse.core.models import Cluster
+
+
+class ClusterSerializer(serializers.ModelSerializer):
+ template_bot = BotSerializer()
+
+ class Meta:
+ model = Cluster
+ fields = [
+ "template_bot",
+ "share",
+ "breakpoint",
+ "autoscale",
+ ]
diff --git a/django_napse/api/fleets/serializers/fleet_serializers.py b/django_napse/api/fleets/serializers/fleet_serializers.py
index a2138c8d..d8fa5831 100644
--- a/django_napse/api/fleets/serializers/fleet_serializers.py
+++ b/django_napse/api/fleets/serializers/fleet_serializers.py
@@ -2,12 +2,18 @@
from rest_framework.fields import empty
from django_napse.api.bots.serializers import BotSerializer
+from django_napse.api.fleets.serializers.cluster_serialisers import ClusterSerializer
from django_napse.core.models import ConnectionWallet, Fleet
class FleetSerializer(serializers.ModelSerializer):
value = serializers.SerializerMethodField(read_only=True)
bot_count = serializers.SerializerMethodField(read_only=True)
+ clusters = ClusterSerializer(
+ write_only=True,
+ many=True,
+ required=True,
+ )
class Meta:
model = Fleet
@@ -20,8 +26,6 @@ class Meta:
]
read_only_fields = [
"uuid",
- "value",
- "bot_count",
]
def __init__(self, instance=None, data=empty, space=None, **kwargs):
diff --git a/django_napse/api/fleets/views/fleet_view.py b/django_napse/api/fleets/views/fleet_view.py
index 72831927..15afa269 100644
--- a/django_napse/api/fleets/views/fleet_view.py
+++ b/django_napse/api/fleets/views/fleet_view.py
@@ -5,7 +5,7 @@
from django_napse.api.custom_permissions import HasSpace
from django_napse.api.custom_viewset import CustomViewSet
from django_napse.api.fleets.serializers import FleetDetailSerializer, FleetSerializer
-from django_napse.core.models import NapseSpace
+from django_napse.core.models import Fleet, NapseSpace
class FleetView(CustomViewSet):
@@ -13,7 +13,10 @@ class FleetView(CustomViewSet):
serializer_class = FleetSerializer
def get_queryset(self):
- self.space = NapseSpace.objects.get(uuid=self.request.query_params["space"])
+ space_uuid = self.request.query_params.get("space", None)
+ if space_uuid is None:
+ return Fleet.objects.all()
+ self.space = NapseSpace.objects.get(uuid=space_uuid)
return self.space.fleets
def get_serialiser_class(self, *args, **kwargs):
@@ -25,6 +28,13 @@ def get_serialiser_class(self, *args, **kwargs):
result = actions.get(self.action)
return result if result else super().get_serializer_class()
+ def get_permissions(self):
+ match self.action:
+ case "list" | "create":
+ return [HasAPIKey()]
+ case _:
+ return super().get_permissions()
+
def list(self, request):
serializer = self.serializer_class(self.get_queryset(), many=True, space=self.space)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -34,6 +44,7 @@ def retrieve(self, request, pk=None):
def create(self, request, *args, **kwargs):
return Response(status=status.HTTP_501_NOT_IMPLEMENTED)
+ # serializer = self.serializer_class(data=request.data, space=self.space)
def delete(self):
return Response(status=status.HTTP_501_NOT_IMPLEMENTED)
diff --git a/django_napse/api/spaces/views/space_view.py b/django_napse/api/spaces/views/space_view.py
index 4a7da171..c34250cd 100644
--- a/django_napse/api/spaces/views/space_view.py
+++ b/django_napse/api/spaces/views/space_view.py
@@ -1,13 +1,11 @@
from rest_framework import status
-from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_api_key.permissions import HasAPIKey
from django_napse.api.custom_permissions import HasFullAccessPermission, HasMasterKey, HasReadPermission
from django_napse.api.custom_viewset import CustomViewSet
-from django_napse.api.exchanges.serializers.exchange_account_serializer import ExchangeAccountSerializer
from django_napse.api.spaces.serializers import SpaceDetailSerializer, SpaceSerializer
-from django_napse.core.models import ExchangeAccount, NapseSpace
+from django_napse.core.models import NapseSpace
from django_napse.utils.errors import SpaceError
@@ -38,7 +36,7 @@ def get_permissions(self):
return [HasReadPermission()]
case "list":
return [HasAPIKey()]
- case "possible_exchange_accounts" | "create":
+ case "create":
return [HasMasterKey()]
case _:
@@ -82,11 +80,3 @@ def delete(self, request, *args, **kwargs):
except SpaceError.DeleteError:
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
return Response(status=status.HTTP_204_NO_CONTENT)
-
- @action(detail=False, methods=["GET"])
- def possible_exchange_accounts(self, request):
- serialized_exchange_account = ExchangeAccountSerializer(ExchangeAccount.objects.all(), many=True)
- return Response(
- serialized_exchange_account.data,
- status=status.HTTP_200_OK,
- )
diff --git a/django_napse/core/models/fleets/cluster.py b/django_napse/core/models/fleets/cluster.py
index fbfb026a..b75c17e4 100644
--- a/django_napse/core/models/fleets/cluster.py
+++ b/django_napse/core/models/fleets/cluster.py
@@ -8,8 +8,16 @@
class Cluster(models.Model):
- fleet = models.ForeignKey("Fleet", on_delete=models.CASCADE, related_name="clusters")
- template_bot = models.OneToOneField("Bot", on_delete=models.CASCADE, related_name="cluster")
+ fleet = models.ForeignKey(
+ "Fleet",
+ on_delete=models.CASCADE,
+ related_name="clusters",
+ )
+ template_bot = models.OneToOneField(
+ "Bot",
+ on_delete=models.CASCADE,
+ related_name="cluster",
+ )
share = models.FloatField()
breakpoint = models.FloatField()
autoscale = models.BooleanField()
diff --git a/django_napse/core/models/fleets/fleet.py b/django_napse/core/models/fleets/fleet.py
index 2def824e..c603847d 100644
--- a/django_napse/core/models/fleets/fleet.py
+++ b/django_napse/core/models/fleets/fleet.py
@@ -11,12 +11,25 @@
class Fleet(models.Model):
- uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
- name = models.CharField(max_length=100, default="Fleet")
- exchange_account = models.ForeignKey("ExchangeAccount", on_delete=models.CASCADE)
+ uuid = models.UUIDField(
+ default=uuid.uuid4,
+ editable=False,
+ unique=True,
+ )
+ name = models.CharField(
+ max_length=100,
+ default="Fleet",
+ )
+ exchange_account = models.ForeignKey(
+ "ExchangeAccount",
+ on_delete=models.CASCADE,
+ )
running = models.BooleanField(default=False)
setup_finished = models.BooleanField(default=False)
- created_at = models.DateTimeField(auto_now_add=True, blank=True)
+ created_at = models.DateTimeField(
+ auto_now_add=True,
+ blank=True,
+ )
objects = FleetManager()
diff --git a/django_napse/utils/api_test_case.py b/django_napse/utils/api_test_case.py
index ffd8db89..5db965b8 100644
--- a/django_napse/utils/api_test_case.py
+++ b/django_napse/utils/api_test_case.py
@@ -96,42 +96,42 @@ def run_tests(self, mode):
self.check_auth(name="No permissions", mode=mode, error_list=error_list, divider=100, expected=2)
else:
if HasSpace in permissions:
- self.check_auth(name="HasSpace \w no key", mode=mode, error_list=error_list, expected=400)
+ self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400)
self.build_url(kwargs={"space": str("random uuid"), **self.kwargs})
- self.check_auth(name="HasSpace \w no key", mode=mode, error_list=error_list, expected=400)
+ self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400)
self.build_url(kwargs={"space": str("7aafc68d-f619-4874-aaf5-c123a176e303"), **self.kwargs})
- self.check_auth(name="HasSpace \w no key", mode=mode, error_list=error_list, expected=400)
+ self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400)
self.build_url(kwargs={"space": str(self.space.uuid), **self.kwargs})
if HasAPIKey in permissions:
- self.check_auth(name="HasAPIKey \w no key", mode=mode, error_list=error_list)
+ self.check_auth(name="HasAPIKey with no key", mode=mode, error_list=error_list)
if HasReadPermission in permissions:
- self.check_auth(name="HasReadPermission \w no key", mode=mode, error_list=error_list)
+ self.check_auth(name="HasReadPermission with no key", mode=mode, error_list=error_list)
self.build_key([])
self.authenticate()
- self.check_auth(name="HasReadPermission \w no permissions", mode=mode, error_list=error_list)
+ self.check_auth(name="HasReadPermission with no permissions", mode=mode, error_list=error_list)
if HasFullAccessPermission in permissions:
- self.check_auth(name="HasFullAccessPermission \w no key", mode=mode, error_list=error_list)
+ self.check_auth(name="HasFullAccessPermission with no key", mode=mode, error_list=error_list)
self.build_key([])
self.authenticate()
- self.check_auth(name="HasFullAccessPermission \w no permissions", mode=mode, error_list=error_list)
+ self.check_auth(name="HasFullAccessPermission with no permissions", mode=mode, error_list=error_list)
self.build_key([HasReadPermission])
self.authenticate()
- self.check_auth(name="HasFullAccessPermission \w read permissions", mode=mode, error_list=error_list)
+ self.check_auth(name="HasFullAccessPermission with read permissions", mode=mode, error_list=error_list)
if HasAdminPermission in permissions:
- self.check_auth(name="HasAdminPermission \w no key", mode=mode, error_list=error_list)
+ self.check_auth(name="HasAdminPermission with no key", mode=mode, error_list=error_list)
self.build_key([])
self.authenticate()
- self.check_auth(name="HasAdminPermission \w no permissions", mode=mode, error_list=error_list)
+ self.check_auth(name="HasAdminPermission with no permissions", mode=mode, error_list=error_list)
self.build_key([HasReadPermission])
self.authenticate()
- self.check_auth(name="HasAdminPermission \w read permissions", mode=mode, error_list=error_list)
+ self.check_auth(name="HasAdminPermission with read permissions", mode=mode, error_list=error_list)
self.build_key([HasReadPermission, HasFullAccessPermission])
self.authenticate()
- self.check_auth(name="HasAdminPermission \w read and full access permissions", mode=mode, error_list=error_list)
+ self.check_auth(name="HasAdminPermission with read and full access permissions", mode=mode, error_list=error_list)
self.build_key(permissions)
self.authenticate()
diff --git a/docs/contributing.md b/docs/contributing.md
index f270ac66..5c67726c 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -64,13 +64,19 @@ You can commit the code from your fork through a pull request on the official re
=== "Linux"
```bash
- source setup-unix.sh
+ source setup/setup-unix.sh
+ ```
+
+=== "MacOS"
+
+ ```bash
+ source setup/setup-osx.sh
```
=== "Windows"
```powershell
- .\setup-windows.ps1
+ .\setup\setup-windows.ps1
```
#### Setup initial exchange accounts
@@ -89,9 +95,11 @@ At `tests/test_app/`, build a `secret.json` file (or run the `./setup_secrets.sh
}
}
}
-
```
+!!! note
+ We **strongly recommend** to add the `secret.json` file to your `.gitignore` file to avoid sharing your API keys.
+
#### Run
```bash
diff --git a/docs/index.md b/docs/index.md
index 5de2077f..c3f8c100 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,13 +22,19 @@
# Welcome to django-napse's documentation!
+Django napse is a portfolio management module using trading bots.
+
## Installation
+---
+
+This project can be used as a django module, which you can install as follows:
-To install the latest version of django-napse:
```bash
pip install django-napse
```
+Or you can use it as a local backend for the [Napse desktop application](https://github.com/napse-invest/Napse), by cloning the repo (possibly after forking it).
+
### Setup initial exchange accounts
To make full use of the project, we recommend that you fill in the API keys of at least one exchange (among the django-napse [compabile exchanges](#compatible-exchanges)).
@@ -38,7 +44,7 @@ At the root of your project, build a `secret.json` file. Here is an exemple with
{
"Exchange Accounts": {
"Binance EA_NAME": {
- "exchange": "BINANCE", # Name of your exchange (BINANCE, DYDX, ...)
+ "exchange": "BINANCE", # Name of your exchange
"testing": true,
"public_key": "YOUR_PUBLIC_KEY",
"private_key": "YOUR_PRIVATE_KEY"
@@ -46,10 +52,27 @@ At the root of your project, build a `secret.json` file. Here is an exemple with
}
}
```
-We **strongly** recommand you to add the `secret.json` file to your `.gitignore`.
-
+??? note "Note for developers"
+ We **strongly recommend** to add the `secret.json` file to your `.gitignore` file to avoid sharing your API keys.
## Use django-napse
+---
+
+### Local backend
+
+If you want to use django-napse as a local backend for the Napse desktop application, clone the repository and setup the project:
+```bash
+make setup
+```
+
+Then, you can run the server:
+```bash
+make up
+```
+
+Please check the documentation for more information about [endpoints](https://napse-invest.github.io/django-napse/api/).
+
+### Django module
After the installation step, in a `.py` file you can use django-napse after importing it:
```python
@@ -59,6 +82,7 @@ exchange_account_query = ExchangeAccount.objects.all()
```
## Miscellaneous
+---
### Compatible exchanges
diff --git a/docs/plugins/griffe_doclinks.py b/docs/plugins/griffe_doclinks.py
new file mode 100644
index 00000000..00dca481
--- /dev/null
+++ b/docs/plugins/griffe_doclinks.py
@@ -0,0 +1,85 @@
+import ast
+import re
+from functools import partial
+from pathlib import Path
+from typing import Tuple
+
+from griffe.dataclasses import Object as GriffeObject
+from griffe.extensions import VisitorExtension
+from pymdownx.slugs import slugify
+
+DOCS_PATH = Path(__file__).parent.parent
+slugifier = slugify(case="lower")
+
+
+def find_heading(content: str, slug: str, file_path: Path) -> Tuple[str, int]:
+ for m in re.finditer("^#+ (.+)", content, flags=re.M):
+ heading = m.group(1)
+ h_slug = slugifier(heading, "-")
+ if h_slug == slug:
+ return heading, m.end()
+ msg = f"heading with slug {slug!r} not found in {file_path}"
+ raise ValueError(msg)
+
+
+def insert_at_top(path: str, api_link: str) -> str:
+ rel_file = path.rstrip("/") + ".md"
+ file_path = DOCS_PATH / rel_file
+ content = file_path.read_text()
+ second_heading = re.search("^#+ ", content, flags=re.M)
+ assert second_heading, "unable to find second heading in file" # noqa: S101
+ first_section = content[: second_heading.start()]
+
+ if f"[{api_link}]" not in first_section:
+ print(f'inserting API link "{api_link}" at the top of {file_path.relative_to(DOCS_PATH)}')
+ file_path.write_text('??? api "API Documentation"\n' f" [`{api_link}`][{api_link}]
\n\n{content}") # noqa: ISC001
+
+ heading = file_path.stem.replace("_", " ").title()
+ return f'!!! abstract "Usage Documentation"\n [{heading}](../{rel_file})\n'
+
+
+def replace_links(m: re.Match, *, api_link: str) -> str:
+ path_group = m.group(1)
+ if "#" not in path_group:
+ # no heading id, put the content at the top of the page
+ return insert_at_top(path_group, api_link)
+
+ usage_path, slug = path_group.split("#", 1)
+ rel_file = usage_path.rstrip("/") + ".md"
+ file_path = DOCS_PATH / rel_file
+ content = file_path.read_text()
+ heading, heading_end = find_heading(content, slug, file_path)
+
+ next_heading = re.search("^#+ ", content[heading_end:], flags=re.M)
+ next_section = content[heading_end : heading_end + next_heading.start()] if next_heading else content[heading_end:]
+
+ if f"[{api_link}]" not in next_section:
+ print(f'inserting API link "{api_link}" into {file_path.relative_to(DOCS_PATH)}')
+ file_path.write_text(
+ f"{content[:heading_end]}\n\n" '??? api "API Documentation"\n' f" [`{api_link}`][{api_link}]
" f"{content[heading_end:]}", # noqa: ISC001
+ )
+
+ return f'!!! abstract "Usage Documentation"\n [{heading}](../{rel_file}#{slug})\n'
+
+
+def update_docstring(obj: GriffeObject) -> str:
+ return re.sub(
+ r"usage[\- ]docs: ?https://docs\.pydantic\.dev/.+?/(\S+)",
+ partial(replace_links, api_link=obj.path),
+ obj.docstring.value,
+ flags=re.I,
+ )
+
+
+def update_docstrings_recursively(obj: GriffeObject) -> None:
+ if obj.docstring:
+ obj.docstring.value = update_docstring(obj)
+ for member in obj.members.values():
+ if not member.is_alias:
+ update_docstrings_recursively(member)
+
+
+class Extension(VisitorExtension):
+ def visit_module(self, node: ast.AST) -> None:
+ module = self.visitor.current.module
+ update_docstrings_recursively(module)
diff --git a/docs/api.md b/docs/sources/api.md
similarity index 100%
rename from docs/api.md
rename to docs/sources/api.md
diff --git a/docs/theme/partials/header.html b/docs/theme/partials/header.html
deleted file mode 100644
index d301d733..00000000
--- a/docs/theme/partials/header.html
+++ /dev/null
@@ -1,63 +0,0 @@
-{#-
- This file was automatically generated - do not edit
- -#}
- {% set class = "md-header" %}
- {% if "navigation.tabs.sticky" in features %}
- {% set class = class ~ " md-header--shadow md-header--lifted" %}
- {% elif "navigation.tabs" not in features %}
- {% set class = class ~ " md-header--shadow" %}
- {% endif %}
-
-
- {% if "navigation.tabs.sticky" in features %}
- {% if "navigation.tabs" in features %}
- {% include "partials/tabs.html" %}
- {% endif %}
- {% endif %}
-
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index a3ff48c9..b5fc1435 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -7,8 +7,10 @@ copyright: MIT Licence
docs_dir: "docs/"
extra_css:
- - theme/assets/stylesheets/extra.css
- - theme/assets/stylesheets/api.css
+ - assets/stylesheets/extra.css
+ - assets/stylesheets/api.css
+ - assets/stylesheets/docstring.css
+
extra:
social:
@@ -67,6 +69,12 @@ theme:
- navigation.top
- content.code.copy
- search.suggest
+ - content.tabs.link
+ - content.code.annotate
+
+watch:
+ - django_napse
+ - docs
plugins:
- search
@@ -83,26 +91,38 @@ plugins:
python:
paths: [.]
options:
+ members_order: source
+ separate_signature: true
+ filters: ["!^_"]
+ docstring_options:
+ ignore_init_summary: true
+ merge_init_into_class: true
+ heading_level: 3
+ # extensions:
+ # - docs/plugins/griffe_doclinks.py
show_source: false
allow_inspection: false
show_bases: false
show_root_heading: false
- docstring_style: google
- docstring_section_style: list
- docstring_options:
- ignore_init_summary: false
- trim_doctest_flags: true
- heading_level: 3
+ docstring_style: google
+ # docstring_section_style: list
+
+ # Signature
+ show_signature: true
+ separate_signature: true
+ show_signature_annotations: true
+ annotations_path: full
+ line_length: 80
# Contributors
- neoteroi.contribs:
contributors:
- - email: firenix.nex@gmail.com
+ - email: Firenix.nex@gmail.com
image: https://avatars.githubusercontent.com/u/11559668?s=400&u=6564188698fbd519f21b7f400e522659e41a158e&v=4
markdown_extensions:
- toc:
- permalink: "#"
+ permalink: true
- pymdownx.snippets:
- pymdownx.magiclink:
- attr_list:
@@ -122,8 +142,10 @@ markdown_extensions:
alternate_style: true
- pymdownx.highlight:
anchor_linenums: true
+ pygments_lang_class: true
# line_spans: __span
- # pygments_lang_class: true
+
+
nav:
- Home:
@@ -145,8 +167,8 @@ nav:
- "sources/reference/spaces.md"
- "sources/reference/transactions.md"
- "sources/reference/wallets.md"
- - API: "api.md"
+ - API: "sources/api.md"
- Development:
- Contributing: "contributing.md"
- Security: "SECURITY.md"
- - Coverage: coverage.md
\ No newline at end of file
+ - Coverage: coverage.md
diff --git a/requirements/core.txt b/requirements/core.txt
index 488ee6e2..b8daf8aa 100644
--- a/requirements/core.txt
+++ b/requirements/core.txt
@@ -2,7 +2,7 @@ django==4.2.7 # https://www.djangoproject.com/
django-environ==0.11.2 # https://github.com/joke2k/django-environ
django-celery-beat==2.5.0 # https://github.com/celery/django-celery-beat
drf-spectacular==0.26.5 # https://github.com/tfranzel/drf-spectacular
-django-cors-headers==4.3.0 # https://github.com/adamchainz/django-cors-headers
+django-cors-headers==4.3.1 # https://github.com/adamchainz/django-cors-headers
djangorestframework-api-key==3.0.0
psycopg2-binary==2.9.9 # https://github.com/psycopg/psycopg2
diff --git a/requirements/development.txt b/requirements/development.txt
index f71a6b79..ecabce0b 100644
--- a/requirements/development.txt
+++ b/requirements/development.txt
@@ -7,7 +7,7 @@ watchfiles==0.21.0 # https://github.com/samuelcolvin/watchfiles
Werkzeug==3.0.1 # https://github.com/pallets/werkzeug
django-extensions==3.2.3 # https://github.com/django-extensions/django-extensions
django-debug-toolbar==4.2.0 # https://github.com/jazzband/django-debug-toolbar
-ruff==0.1.5 # https://github.com/astral-sh/ruff
+ruff==0.1.8 # https://github.com/astral-sh/ruff
platformdirs==3.11.0 # https://github.com/platformdirs/platformdirs
# Documentation
@@ -19,4 +19,5 @@ neoteroi-mkdocs==1.0.4 # https://github.com/Neoteroi/mkdocs-plugins
drf-spectacular==0.26.5 # https://github.com/tfranzel/drf-spectacular
mkdocstrings==0.24.0 # https://github.com/mkdocstrings/mkdocstrings
mkdocstrings-python==1.7.5 # https://github.com/mkdocstrings/python
+watchfiles==0.21.0 # https://github.com/samuelcolvin/watchfiles
diff --git a/setup-osx.sh b/setup/setup-osx.sh
similarity index 100%
rename from setup-osx.sh
rename to setup/setup-osx.sh
diff --git a/setup-unix.sh b/setup/setup-unix.sh
similarity index 100%
rename from setup-unix.sh
rename to setup/setup-unix.sh
diff --git a/setup-windows.ps1 b/setup/setup-windows.ps1
similarity index 100%
rename from setup-windows.ps1
rename to setup/setup-windows.ps1