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