Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAS Generation for APIV2 #589

Merged
merged 13 commits into from
Oct 23, 2024
20 changes: 17 additions & 3 deletions .github/workflows/rdme-openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,24 @@ jobs:
rdme-openapi:
runs-on: ubuntu-latest
steps:
- name: Check out repo 📚
uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: install pipenv
run: |
pip install pipenv

- name: install dependencies
run: |
pipenv install

- name: build oas file
run: |
pipenv run python manage.py spectacular --file openapi-schema.yml

- name: Run `openapi` command for v1🚀
uses: readmeio/rdme@v8
with:
rdme: openapi openapi-schema.yml --key=${{ secrets.README_API_KEY }} --id=641f6d9e0ffbcd06c0e7343c
rdme: openapi openapi-schema.yml --key=${{ secrets.README_API_KEY }} --id=6715b7fb7960ee004eb4a8cf
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ server/whoosh_index/
# pyenv env file
.python-version

api/tests/approved_files/*.recieved.*
api/tests/approved_files/*.recieved.*
openapi-schema.yml
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ newrelic = "*"
requests = "*"
whitenoise = "*"
gunicorn = "*"
drf-spectacular = "*"

[dev-packages]
pytest = "*"
Expand Down
691 changes: 475 additions & 216 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ You can use our Dockerfile as inspiration, but it likely will not work without s

After completing a build, you can generate an OAS file to be used by another application.
```bash
pipenv run ./manage.py generateschema --generator_class api.schema_generator.Open5eSchemaGenerator > openapi-schema.yml` to build the OAS file.
pipenv run python manage.py spectacular --color --file openapi-schema.yml` to build the OAS file.
```

# Contributing
Expand Down
17 changes: 0 additions & 17 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from rest_framework import viewsets
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.schemas.openapi import AutoSchema

from api import models
from api import serializers
Expand Down Expand Up @@ -96,7 +95,6 @@ class DocumentViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of documents.
retrieve: API endpoint for returning a particular document.
"""
schema = AutoSchema(operation_id_base='V1Document')
queryset = models.Document.objects.all().order_by("pk")
serializer_class = serializers.DocumentSerializer
search_fields = ['title', 'desc']
Expand All @@ -113,7 +111,6 @@ class SpellViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of spells.
retrieve: API endpoint for returning a particular spell.
"""
schema = AutoSchema(operation_id_base='V1Spell')
queryset = models.Spell.objects.all().order_by("pk")
filterset_class=filters.SpellFilter
serializer_class = serializers.SpellSerializer
Expand All @@ -140,7 +137,6 @@ class SpellListViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of spell lists.
retrieve: API endpoint for returning a particular spell list.
"""
schema = AutoSchema(operation_id_base='V1SpellList')
queryset = models.SpellList.objects.all().order_by("pk")
serializer_class = serializers.SpellListSerializer
filterset_class = filters.SpellListFilter
Expand All @@ -152,7 +148,6 @@ class MonsterViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of monsters.
retrieve: API endpoint for returning a particular monster.
"""
schema = AutoSchema(operation_id_base='V1Monster')
queryset = models.Monster.objects.all().order_by("pk")
filterset_class = filters.MonsterFilter

Expand All @@ -164,7 +159,6 @@ class BackgroundViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of backgrounds.
retrieve: API endpoint for returning a particular background.
"""
schema = AutoSchema(operation_id_base='V1Background')
queryset = models.Background.objects.all().order_by("pk")
serializer_class = serializers.BackgroundSerializer
ordering_fields = '__all__'
Expand All @@ -178,7 +172,6 @@ class PlaneViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of planes.
retrieve: API endpoint for returning a particular plane.
"""
schema = AutoSchema(operation_id_base='V1Plane')
queryset = models.Plane.objects.all().order_by("pk")
serializer_class = serializers.PlaneSerializer
filterset_class = filters.PlaneFilter
Expand All @@ -190,7 +183,6 @@ class SectionViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of sections.
retrieve: API endpoint for returning a particular section.
"""
schema = AutoSchema(operation_id_base='V1Section')
queryset = models.Section.objects.all().order_by("pk")
serializer_class = serializers.SectionSerializer
ordering_fields = '__all__'
Expand All @@ -204,7 +196,6 @@ class FeatViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of feats.
retrieve: API endpoint for returning a particular feat.
"""
schema = AutoSchema(operation_id_base='V1Feat')
queryset = models.Feat.objects.all().order_by("pk")
serializer_class = serializers.FeatSerializer
filterset_class = filters.FeatFilter
Expand All @@ -216,7 +207,6 @@ class ConditionViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of conditions.
retrieve: API endpoint for returning a particular condition.
"""
schema = AutoSchema(operation_id_base='V1Condition')
queryset = models.Condition.objects.all().order_by("pk")
serializer_class = serializers.ConditionSerializer
search_fields = ['name', 'desc']
Expand All @@ -228,7 +218,6 @@ class RaceViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of races.
retrieve: API endpoint for returning a particular race.
"""
schema = AutoSchema(operation_id_base='V1Race')
queryset = models.Race.objects.all().order_by("pk")
serializer_class = serializers.RaceSerializer
filterset_class = filters.RaceFilter
Expand All @@ -241,7 +230,6 @@ class SubraceViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint that allows viewing of Subraces.
retrieve: API endpoint for returning a particular subrace.
"""
schema = AutoSchema(operation_id_base='V1Subrace')
queryset = models.Subrace.objects.all().order_by("pk")
serializer_class = serializers.SubraceSerializer
search_fields = ['name', 'desc']
Expand All @@ -256,7 +244,6 @@ class CharClassViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of classes and archetypes.
retrieve: API endpoint for returning a particular class or archetype.
"""
schema = AutoSchema(operation_id_base='V1Class')
queryset = models.CharClass.objects.all().order_by("pk")
serializer_class = serializers.CharClassSerializer
filterset_class = filters.CharClassFilter
Expand All @@ -269,7 +256,6 @@ class ArchetypeViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint that allows viewing of Archetypes.
retrieve: API endpoint for returning a particular archetype.
"""
schema = AutoSchema(operation_id_base='V1Archetype')
queryset = models.Archetype.objects.all().order_by("pk")
serializer_class = serializers.ArchetypeSerializer
search_fields = ['name', 'desc']
Expand All @@ -284,7 +270,6 @@ class MagicItemViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of magic items.
retrieve: API endpoint for returning a particular magic item.
"""
schema = AutoSchema(operation_id_base='V1MagicItem')
queryset = models.MagicItem.objects.all().order_by("pk")
serializer_class = serializers.MagicItemSerializer
filterset_class = filters.MagicItemFilter
Expand All @@ -296,7 +281,6 @@ class WeaponViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of weapons.
retrieve: API endpoint for returning a particular weapon.
"""
schema = AutoSchema(operation_id_base='V1Weapon')
queryset = models.Weapon.objects.all().order_by("pk")
serializer_class = serializers.WeaponSerializer
filterset_class = filters.WeaponFilter
Expand All @@ -308,7 +292,6 @@ class ArmorViewSet(viewsets.ReadOnlyModelViewSet):
list: API endpoint for returning a list of armor.
retrieve: API endpoint for returning a particular armor.
"""
schema = AutoSchema(operation_id_base='V1Armor')
queryset = models.Armor.objects.all().order_by("pk")
serializer_class = serializers.ArmorSerializer
filterset_class = filters.ArmorFilter
Expand Down
67 changes: 66 additions & 1 deletion api_v2/models/abstracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
from .enums import SAVING_THROW_MAXIMUM, SAVING_THROW_MINIMUM
from .enums import SKILL_BONUS_MINIMUM, SKILL_BONUS_MAXIMUM
from .enums import PASSIVE_SCORE_MAXIMUM
from drf_spectacular.utils import extend_schema_field, inline_serializer
from drf_spectacular.types import OpenApiTypes
from rest_framework import serializers
import decimal

# FIELDS USED ACROSS MULTIPLE MODELS

Expand Down Expand Up @@ -49,7 +53,7 @@ def distance_field(null=True):
return models.FloatField(
null=null,
blank=True,
validators=[MinValueValidator(0)],
validators=[MinValueValidator(decimal.Decimal(0.0))],
help_text="Used to measure distance."
)

Expand Down Expand Up @@ -140,6 +144,7 @@ class HasPrerequisite(models.Model):
help_text='Prerequisite for the game content item.')

@property
@extend_schema_field(OpenApiTypes.BOOL)
def has_prerequisite(self):
return self.prerequisite not in ("", None)

Expand Down Expand Up @@ -202,6 +207,17 @@ class HasAbilities(models.Model):
ability_score_charisma = ability_score_field(
'Integer representing the charisma ability.')

@extend_schema_field(inline_serializer(
name="ability_scores",
fields={
"strength": serializers.IntegerField(),
"dexterity": serializers.IntegerField(),
"constitution": serializers.IntegerField(),
"intelligence": serializers.IntegerField(),
"wisdom": serializers.IntegerField(),
"charisma": serializers.IntegerField(),
})
)
def get_ability_scores(self):
return {
'strength': self.ability_score_strength,
Expand Down Expand Up @@ -239,6 +255,18 @@ def modifier_wisdom(self):
def modifier_charisma(self):
return ability_modifier(self.ability_score_charisma)

@extend_schema_field(inline_serializer(
name="ability_modifiers",
fields={
# todo: technically they're all typed as any, but they're `floor`'d, so they should come out as integers
"strength": serializers.IntegerField(),
"dexterity": serializers.IntegerField(),
"constitution": serializers.IntegerField(),
"intelligence": serializers.IntegerField(),
"wisdom": serializers.IntegerField(),
"charisma": serializers.IntegerField(),
})
)
def get_modifiers(self):
return {
'strength': self.modifier_strength,
Expand Down Expand Up @@ -272,6 +300,19 @@ def get_modifiers(self):
saving_throw_charisma = saving_throw_field(
'Signed integer added to charisma saving throws.')


@extend_schema_field(inline_serializer(
name="saving_throws",
fields={
# todo: all of these are "or none"
"strength": serializers.IntegerField(),
"dexterity": serializers.IntegerField(),
"constitution": serializers.IntegerField(),
"intelligence": serializers.IntegerField(),
"wisdom": serializers.IntegerField(),
"charisma": serializers.IntegerField(),
})
)
def get_saving_throws(self):
return {
'strength': self.saving_throw_strength,
Expand Down Expand Up @@ -341,6 +382,30 @@ def get_saving_throws(self):
skill_bonus_survival = skill_bonus_field(
'Signed integer added to survival skill checks.')

@extend_schema_field(inline_serializer(
name="skill_bonuses",
fields={
# todo: all of these are typed as also none
'acrobatics': serializers.IntegerField(),
'animal_handling': serializers.IntegerField(),
'arcana': serializers.IntegerField(),
'athletics': serializers.IntegerField(),
'deception': serializers.IntegerField(),
'history': serializers.IntegerField(),
'insight': serializers.IntegerField(),
'intimidation': serializers.IntegerField(),
'investigation': serializers.IntegerField(),
'medicine': serializers.IntegerField(),
'nature': serializers.IntegerField(),
'perception': serializers.IntegerField(),
'performance': serializers.IntegerField(),
'persuasion': serializers.IntegerField(),
'religion': serializers.IntegerField(),
'sleight_of_hand': serializers.IntegerField(),
'stealth': serializers.IntegerField(),
'survival': serializers.IntegerField(),
}
))
def get_skill_bonuses(self):
return {
'acrobatics': self.skill_bonus_acrobatics,
Expand Down
6 changes: 5 additions & 1 deletion api_v2/models/alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,27 @@

from .abstracts import HasName, HasDescription
from .document import FromDocument

from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes

class Alignment(HasName, HasDescription, FromDocument):
"""This is the model for an alignment, which is way to describe the
moral and personal attitudes of a creature."""

@property
@extend_schema_field(OpenApiTypes.STR)
def short_name(self):
short_name = ""
for word in self.name.split(" "):
short_name += word[0].upper()
return short_name

@property
@extend_schema_field(OpenApiTypes.STR)
def morality(self):
return self.name.split(" ")[-1].lower()

@property
@extend_schema_field(OpenApiTypes.STR)
def societal_attitude(self):
return self.name.split(" ")[0].lower()
4 changes: 4 additions & 0 deletions api_v2/models/armor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from django.db import models
from .abstracts import HasName
from .document import FromDocument
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes


class Armor(HasName, FromDocument):
Expand Down Expand Up @@ -40,6 +42,7 @@ class Armor(HasName, FromDocument):
help_text='Integer representing the dexterity modifier cap.')

@property
@extend_schema_field(OpenApiTypes.STR)
def category(self):
category = 'heavy'
if self.ac_add_dexmod:
Expand All @@ -50,6 +53,7 @@ def category(self):


@property
@extend_schema_field(OpenApiTypes.STR)
def ac_display(self):
"""Display text for armor class."""
ac_string = str(self.ac_base)
Expand Down
Loading
Loading