Skip to content

Commit 93d9843

Browse files
Dmitry MishanovZnbiz
authored andcommitted
tests for spec plugin
1 parent 7c1f985 commit 93d9843

File tree

6 files changed

+870
-61
lines changed

6 files changed

+870
-61
lines changed

combojsonapi/spec/plugin.py

Lines changed: 70 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from marshmallow import fields, Schema
88
from flask_combo_jsonapi import Api
99
from flask_combo_jsonapi.plugin import BasePlugin
10-
from flask_combo_jsonapi.resource import ResourceList, ResourceDetail
10+
from flask_combo_jsonapi.resource import ResourceList, ResourceDetail, Resource
1111
from flask_combo_jsonapi.utils import SPLIT_REL
1212

1313
from combojsonapi.spec.apispec import DocBlueprintMixin
@@ -34,7 +34,6 @@ def __init__(self, app=None, spec_kwargs=None, decorators=None, tags: Dict[str,
3434
self.app = None
3535
self._fields = []
3636
# Use lists to enforce order
37-
self._definitions = []
3837
self._fields = []
3938
self._converters = []
4039

@@ -69,12 +68,6 @@ def after_init_plugin(self, *args, app=None, **kwargs):
6968
# Register custom fields in spec
7069
for args in self._fields:
7170
self.spec.register_field(*args)
72-
# Register schema definitions in spec
73-
for name, schema_cls, kwargs in self._definitions:
74-
if APISPEC_VERSION_MAJOR < 1:
75-
self.spec.definition(create_schema_name(schema=schema_cls), schema=schema_cls, **kwargs)
76-
else:
77-
self.spec.components.schema(create_schema_name(schema=schema_cls), schema=schema_cls, **kwargs)
7871
# Register custom converters in spec
7972
for args in self._converters:
8073
self.spec.register_converter(*args)
@@ -143,7 +136,8 @@ def param_id(self) -> dict:
143136
"format": "int32",
144137
}
145138

146-
def _get_operations_for_all(self, tag_name, default_parameters) -> Dict[str, Any]:
139+
@classmethod
140+
def _get_operations_for_all(cls, tag_name: str, default_parameters: list) -> Dict[str, Any]:
147141
"""
148142
Creating base dict
149143
@@ -153,11 +147,12 @@ def _get_operations_for_all(self, tag_name, default_parameters) -> Dict[str, Any
153147
"""
154148
return {
155149
"tags": [tag_name],
156-
"produces": ["application/json",],
150+
"produces": ["application/json"],
157151
"parameters": default_parameters if default_parameters else [],
158152
}
159153

160-
def __get_parameters_for_include_models(self, resource) -> dict:
154+
@classmethod
155+
def __get_parameters_for_include_models(cls, resource: Resource) -> dict:
161156
fields_names = [
162157
i_field_name
163158
for i_field_name, i_field in resource.schema._declared_fields.items()
@@ -174,15 +169,16 @@ def __get_parameters_for_include_models(self, resource) -> dict:
174169
"description": f"Related relationships to include.\nAvailable:\n{example_models_for_include}",
175170
}
176171

177-
def __get_parameters_for_sparse_fieldsets(self, resource, description) -> dict:
172+
@classmethod
173+
def __get_parameters_for_sparse_fieldsets(cls, resource: Resource, description: str) -> dict:
178174
# Sparse Fieldsets
179175
return {
180176
"name": f"fields[{resource.schema.Meta.type_}]",
181177
"in": "query",
182178
"type": "array",
183179
"required": False,
184180
"description": description.format(resource.schema.Meta.type_),
185-
"items": {"type": "string", "enum": list(resource.schema._declared_fields.keys()),},
181+
"items": {"type": "string", "enum": list(resource.schema._declared_fields.keys())},
186182
}
187183

188184
def __get_parameters_for_declared_fields(self, resource, description) -> Generator[dict, None, None]:
@@ -234,7 +230,8 @@ def __list_filters_data(self) -> tuple:
234230
},
235231
)
236232

237-
def __update_parameter_for_field_spec(self, new_param: dict, fld_sped: dict) -> None:
233+
@classmethod
234+
def _update_parameter_for_field_spec(cls, new_param: dict, fld_sped: dict) -> None:
238235
"""
239236
:param new_param:
240237
:param fld_sped:
@@ -256,7 +253,7 @@ def __get_parameter_for_not_nested(self, field_name, field_spec) -> dict:
256253
"required": False,
257254
"description": f"{field_name} attribute filter",
258255
}
259-
self.__update_parameter_for_field_spec(new_parameter, field_spec)
256+
self._update_parameter_for_field_spec(new_parameter, field_spec)
260257
return new_parameter
261258

262259
def __get_parameter_for_nested_with_filtering(self, field_name, field_jsonb_name, field_jsonb_spec):
@@ -267,7 +264,7 @@ def __get_parameter_for_nested_with_filtering(self, field_name, field_jsonb_name
267264
"required": False,
268265
"description": f"{field_name}{SPLIT_REL}{field_jsonb_name} attribute filter",
269266
}
270-
self.__update_parameter_for_field_spec(new_parameter, field_jsonb_spec)
267+
self._update_parameter_for_field_spec(new_parameter, field_jsonb_spec)
271268
return new_parameter
272269

273270
def __get_parameters_for_nested_with_filtering(self, field, field_name) -> Generator[dict, None, None]:
@@ -326,6 +323,60 @@ def _get_operations_for_get(self, resource, tag_name, default_parameters):
326323

327324
return operations_get
328325

326+
def _get_operations_for_post(self, schema: dict, tag_name: str, default_parameters: list) -> dict:
327+
operations = self._get_operations_for_all(tag_name, default_parameters)
328+
operations["responses"] = {
329+
"201": {"description": "Created"},
330+
"202": {"description": "Accepted"},
331+
"403": {"description": "This implementation does not accept client-generated IDs"},
332+
"404": {"description": "Not Found"},
333+
"409": {"description": "Conflict"},
334+
}
335+
operations["parameters"].append(
336+
{
337+
"name": "POST body",
338+
"in": "body",
339+
"schema": schema,
340+
"required": True,
341+
"description": f"{tag_name} attributes",
342+
}
343+
)
344+
return operations
345+
346+
def _get_operations_for_patch(self, schema: dict, tag_name: str, default_parameters: list) -> dict:
347+
operations = self._get_operations_for_all(tag_name, default_parameters)
348+
operations["responses"] = {
349+
"200": {"description": "Success"},
350+
"201": {"description": "Created"},
351+
"204": {"description": "No Content"},
352+
"403": {"description": "Forbidden"},
353+
"404": {"description": "Not Found"},
354+
"409": {"description": "Conflict"},
355+
}
356+
operations["parameters"].append(self.param_id)
357+
operations["parameters"].append(
358+
{
359+
"name": "POST body",
360+
"in": "body",
361+
"schema": schema,
362+
"required": True,
363+
"description": f"{tag_name} attributes",
364+
}
365+
)
366+
return operations
367+
368+
def _get_operations_for_delete(self, tag_name: str, default_parameters: list) -> dict:
369+
operations = self._get_operations_for_all(tag_name, default_parameters)
370+
operations["parameters"].append(self.param_id)
371+
operations["responses"] = {
372+
"200": {"description": "Success"},
373+
"202": {"description": "Accepted"},
374+
"204": {"description": "No Content"},
375+
"403": {"description": "Forbidden"},
376+
"404": {"description": "Not Found"},
377+
}
378+
return operations
379+
329380
def _add_paths_in_spec(
330381
self,
331382
path: str = "",
@@ -372,53 +423,11 @@ def _add_paths_in_spec(
372423
if "get" in methods:
373424
operations["get"] = self._get_operations_for_get(resource, tag_name, default_parameters)
374425
if "post" in methods:
375-
operations["post"] = self._get_operations_for_all(tag_name, default_parameters)
376-
operations["post"]["responses"] = {
377-
"201": {"description": "Created"},
378-
"202": {"description": "Accepted"},
379-
"403": {"description": "This implementation does not accept client-generated IDs"},
380-
"404": {"description": "Not Found"},
381-
"409": {"description": "Conflict"},
382-
}
383-
operations["post"]["parameters"].append(
384-
{
385-
"name": "POST body",
386-
"in": "body",
387-
"schema": schema,
388-
"required": True,
389-
"description": f"{tag_name} attributes",
390-
}
391-
)
426+
operations["post"] = self._get_operations_for_post(schema, tag_name, default_parameters)
392427
if "patch" in methods:
393-
operations["patch"] = self._get_operations_for_all(tag_name, default_parameters)
394-
operations["patch"]["responses"] = {
395-
"200": {"description": "Success"},
396-
"201": {"description": "Created"},
397-
"204": {"description": "No Content"},
398-
"403": {"description": "Forbidden"},
399-
"404": {"description": "Not Found"},
400-
"409": {"description": "Conflict"},
401-
}
402-
operations["patch"]["parameters"].append(self.param_id)
403-
operations["patch"]["parameters"].append(
404-
{
405-
"name": "POST body",
406-
"in": "body",
407-
"schema": schema,
408-
"required": True,
409-
"description": f"{tag_name} attributes",
410-
}
411-
)
428+
operations["patch"] = self._get_operations_for_patch(schema, tag_name, default_parameters)
412429
if "delete" in methods:
413-
operations["delete"] = self._get_operations_for_all(tag_name, default_parameters)
414-
operations["delete"]["parameters"].append(self.param_id)
415-
operations["delete"]["responses"] = {
416-
"200": {"description": "Success"},
417-
"202": {"description": "Accepted"},
418-
"204": {"description": "No Content"},
419-
"403": {"description": "Forbidden"},
420-
"404": {"description": "Not Found"},
421-
}
430+
operations["delete"] = self._get_operations_for_delete(tag_name, default_parameters)
422431
rule = None
423432
for i_rule in self.app.url_map._rules:
424433
if i_rule.rule == path:

tests/test_spec/__init__.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from flask import Flask
2+
from flask_combo_jsonapi import ResourceList, ResourceDetail
3+
from marshmallow import Schema, fields
4+
from sqlalchemy import Integer, Column, String, create_engine
5+
from sqlalchemy.ext.declarative import declarative_base
6+
from sqlalchemy.orm import sessionmaker
7+
8+
from combojsonapi.utils import Relationship
9+
10+
Base = declarative_base()
11+
12+
engine = create_engine("sqlite:///:memory:")
13+
session = sessionmaker(bind=engine)()
14+
15+
16+
class RelatedModel(Base):
17+
__tablename__ = 'related_model'
18+
19+
id = Column(Integer, primary_key=True)
20+
name = Column(String)
21+
22+
23+
class SomeModel(Base):
24+
__tablename__ = 'some_model'
25+
26+
id = Column(Integer, primary_key=True)
27+
name = Column(String)
28+
type = Column(Integer)
29+
flags = Column(Integer)
30+
description = Column(String)
31+
settings = Column(String)
32+
related_model_id = Column(Integer)
33+
34+
35+
class RelatedModelSchema(Schema):
36+
class Meta:
37+
model = RelatedModel
38+
type_ = 'related_model'
39+
filtering = True
40+
41+
id = fields.Integer()
42+
name = fields.String()
43+
44+
45+
class SomeSchema(Schema):
46+
class Meta:
47+
model = SomeModel
48+
type_ = 'some_schema'
49+
50+
id = fields.Integer()
51+
name = fields.String()
52+
type = fields.Integer()
53+
flags = fields.Integer()
54+
description = fields.Integer()
55+
related_model_id = Relationship(nested=RelatedModelSchema, schema=RelatedModelSchema)
56+
57+
58+
class SchemaReversedName(Schema):
59+
class Meta:
60+
model = SomeModel
61+
type_ = 'schema_reversed_name'
62+
63+
id = fields.Integer(description='just id field')
64+
name = fields.String(description='just name field')
65+
array_field = fields.List(fields.Integer, description='just array field with integers')
66+
67+
68+
class SomeResourceDetail(ResourceDetail):
69+
schema = SomeSchema
70+
data_layer = {
71+
'session': session,
72+
'model': SomeModel,
73+
}
74+
75+
76+
class SomeResourceList(ResourceList):
77+
schema = SomeSchema
78+
data_layer = {
79+
'session': session,
80+
'model': SomeModel,
81+
}
82+
83+
84+
app = Flask(__name__)
85+
app.add_url_rule('/apispec/some_url', view_func=SomeResourceDetail.as_view('some_view123'))

0 commit comments

Comments
 (0)