├── unittest.cfg
├── doc
├── graphiql.png
├── data_model.png
├── architecture.png
├── data_model.xml
└── architecture.xml
├── requirements.txt
├── example
├── utils.py
├── api.py
├── database
│ ├── base.py
│ ├── model_people.py
│ ├── model_planet.py
│ └── data
│ │ ├── planet.json
│ │ └── people.json
├── schema.py
├── setup.py
├── schema_people.py
└── schema_planet.py
├── test
├── junit
│ └── test-results.xml
└── test_api.py
├── .gitignore
├── README.md
├── .circleci
└── config.yml
└── LICENSE
/unittest.cfg:
--------------------------------------------------------------------------------
1 | [junit-xml]
2 | path = test/junit/test-results.xml
3 |
--------------------------------------------------------------------------------
/doc/graphiql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexisrolland/flask-graphene-sqlalchemy/HEAD/doc/graphiql.png
--------------------------------------------------------------------------------
/doc/data_model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexisrolland/flask-graphene-sqlalchemy/HEAD/doc/data_model.png
--------------------------------------------------------------------------------
/doc/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexisrolland/flask-graphene-sqlalchemy/HEAD/doc/architecture.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | flask>=1.0.0
2 | flask-graphql==1.4.1
3 | graphene==2.0.0
4 | graphene-sqlalchemy==2.0.0
5 | nose2==0.7.4
6 | requests==2.20.0
7 | sqlalchemy==1.3.0
8 |
--------------------------------------------------------------------------------
/example/utils.py:
--------------------------------------------------------------------------------
1 | from graphql_relay.node.node import from_global_id
2 |
3 |
4 | def input_to_dictionary(input):
5 | """Method to convert Graphene inputs into dictionary."""
6 | dictionary = {}
7 | for key in input:
8 | # Convert GraphQL global id to database id
9 | if key[-2:] == 'id' and input[key] != 'unknown':
10 | input[key] = from_global_id(input[key])[1]
11 | dictionary[key] = input[key]
12 | return dictionary
13 |
--------------------------------------------------------------------------------
/example/api.py:
--------------------------------------------------------------------------------
1 | from database.base import db_session
2 | from flask import Flask
3 | from flask_graphql import GraphQLView
4 | from schema import schema
5 |
6 | app = Flask(__name__)
7 | app.add_url_rule(
8 | '/graphql',
9 | view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True))
10 |
11 |
12 | @app.teardown_appcontext
13 | def shutdown_session(exception=None):
14 | db_session.remove()
15 |
16 |
17 | if __name__ == '__main__':
18 | app.run(threaded=True) # debug=True
19 |
--------------------------------------------------------------------------------
/example/database/base.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.ext.declarative import declarative_base
3 | from sqlalchemy.orm import scoped_session, sessionmaker
4 | import os
5 |
6 | # Create database engine
7 | db_name = 'database.db'
8 | db_path = os.path.join(os.path.dirname(__file__), db_name)
9 | db_uri = 'sqlite:///{}'.format(db_path)
10 | engine = create_engine(db_uri, convert_unicode=True)
11 |
12 | # Declarative base model to create database tables and classes
13 | Base = declarative_base()
14 | Base.metadata.bind = engine # Bind engine to metadata of the base class
15 |
16 | # Create database session object
17 | db_session = scoped_session(sessionmaker(bind=engine, expire_on_commit=False))
18 | Base.query = db_session.query_property() # Used by graphql to execute queries
19 |
--------------------------------------------------------------------------------
/example/schema.py:
--------------------------------------------------------------------------------
1 | from graphene_sqlalchemy import SQLAlchemyConnectionField
2 | import graphene
3 | import schema_planet
4 | import schema_people
5 |
6 |
7 | class Query(graphene.ObjectType):
8 | """Nodes which can be queried by this API."""
9 | node = graphene.relay.Node.Field()
10 |
11 | # People
12 | people = graphene.relay.Node.Field(schema_people.People)
13 | peopleList = SQLAlchemyConnectionField(schema_people.People)
14 |
15 | # Planet
16 | planet = graphene.relay.Node.Field(schema_planet.Planet)
17 | planetList = SQLAlchemyConnectionField(schema_planet.Planet)
18 |
19 |
20 | class Mutation(graphene.ObjectType):
21 | """Mutations which can be performed by this API."""
22 | # Person mutation
23 | createPerson = schema_people.CreatePerson.Field()
24 | updatePerson = schema_people.UpdatePerson.Field()
25 |
26 | # Planet mutations
27 | createPlanet = schema_planet.CreatePlanet.Field()
28 | updatePlanet = schema_planet.UpdatePlanet.Field()
29 |
30 |
31 | schema = graphene.Schema(query=Query, mutation=Mutation)
32 |
--------------------------------------------------------------------------------
/test/junit/test-results.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/setup.py:
--------------------------------------------------------------------------------
1 | from ast import literal_eval
2 | from database.model_people import ModelPeople
3 | from database.model_planet import ModelPlanet
4 | from database import base
5 | import logging
6 | import sys
7 |
8 | # Load logging configuration
9 | log = logging.getLogger(__name__)
10 | logging.basicConfig(
11 | stream=sys.stdout,
12 | level=logging.INFO,
13 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
14 |
15 |
16 | if __name__ == '__main__':
17 | log.info('Create database {}'.format(base.db_name))
18 | base.Base.metadata.create_all(base.engine)
19 |
20 | log.info('Insert Planet data in database')
21 | with open('database/data/planet.json', 'r') as file:
22 | data = literal_eval(file.read())
23 | for record in data:
24 | planet = ModelPlanet(**record)
25 | base.db_session.add(planet)
26 | base.db_session.commit()
27 |
28 | log.info('Insert People data in database')
29 | with open('database/data/people.json', 'r') as file:
30 | data = literal_eval(file.read())
31 | for record in data:
32 | planet = ModelPeople(**record)
33 | base.db_session.add(planet)
34 | base.db_session.commit()
35 |
--------------------------------------------------------------------------------
/example/database/model_people.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 | from sqlalchemy import Column, ForeignKey, Integer, String
3 |
4 |
5 | class ModelPeople(Base):
6 | """People model."""
7 |
8 | __tablename__ = 'people'
9 |
10 | id = Column('id', Integer, primary_key=True, doc="Id of the person.")
11 | name = Column('name', String, doc="Name of the person.")
12 | height = Column('height', String, doc="Height of the person.")
13 | mass = Column('mass', String, doc="Mass of the person.")
14 | hair_color = Column('hair_color', String, doc="Hair color of the person.")
15 | skin_color = Column('skin_color', String, doc="Skin color of the person.")
16 | eye_color = Column('eye_color', String, doc="Eye color of the person.")
17 | birth_year = Column('birth_year', String, doc="Birth year of the person.")
18 | gender = Column('gender', String, doc="Gender of the person.")
19 | planet_id = Column('planet_id', Integer, ForeignKey('planet.id'), doc="Id of the planet from which the person comes from.")
20 | created = Column('created', String, doc="Record created date.")
21 | edited = Column('edited', String, doc="Record last updated date.")
22 | url = Column('url', String, doc="URL of the person in the Star Wars API.")
23 |
--------------------------------------------------------------------------------
/example/database/model_planet.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 | from .model_people import ModelPeople
3 | from sqlalchemy import Column, Integer, String
4 | from sqlalchemy.orm import relationship
5 |
6 |
7 | class ModelPlanet(Base):
8 | """Planet model."""
9 |
10 | __tablename__ = 'planet'
11 |
12 | id = Column('id', Integer, primary_key=True, doc="Id of the person.")
13 | name = Column('name', String, doc="Name of the planet.")
14 | rotation_period = Column('rotation_period', String, doc="Rotation period of the planet.")
15 | orbital_period = Column('orbital_period', String, doc="Orbital period of the planet.")
16 | diameter = Column('diameter', String, doc="Diameter of the planet.")
17 | climate = Column('climate', String, doc="Climate period of the planet.")
18 | gravity = Column('gravity', String, doc="Gravity of the planet.")
19 | terrain = Column('terrain', String, doc="Terrain of the planet.")
20 | surface_water = Column('surface_water', String, doc="Surface water of the planet.")
21 | population = Column('population', String, doc="Population of the planet.")
22 | created = Column('created', String, doc="Record created date.")
23 | edited = Column('edited', String, doc="Record last updated date.")
24 | url = Column('url', String, doc="URL of the planet in the Star Wars API.")
25 |
26 | peopleList = relationship(ModelPeople, backref='planet')
27 |
--------------------------------------------------------------------------------
/doc/data_model.xml:
--------------------------------------------------------------------------------
1 | 7Vltb5swEP41+ZiJQJJlH9u06aSpWrVOW/cpMvgAq8Zmxnnrr98ZzFtI2lSqWraVSgU/d/Y9vgPzxAy8ebK9UiSNryUFPnAduh14FwPXHY3dKZ4MsiuQ2exTAUSKUetUA7fsASzoWHTFKGQtRy0l1yxtg4EUAgLdwohSctN2CyVvR01JBB3gNiC8i/5kVMcWHTlObfgMLIpt6NnEGnwS3EdKroSNN3C9MD8Kc0LKsax/FhMqNw3Iuxx4cyWlLq6S7Ry4yW2ZtqLf4oi14q1A6FM6uEA9F0LHJR/DMc5jaEdYE76CcgpTjmOdU7Y2hPXOJmn6e2VYnvtyO8zYAxPRwDszKZCKghoijA204/9NkUNjzVM4qSzNfBkz5gvG5q/ySAml1dhuuq0D41VkziknAnRJE+eaMy1sJaqJj7QP0T/OLZRCm5mBtULSjO0EmMmKXGNAt+OVpSTY93K686i4+hKfm31QdRBaIjdfGpOv4a4jo486GrAb5pHAJeJ3fJ4gIkgCPaGipCaaSbFMQTFJnznO6/GUymea8L7TpAxLq0H1lmCkyJrpXU/YYKYUYaInbLKVCkkAyw1pVPCNOaUyXfH8Ce0JoUABpqcvKylQ1h8yK8Wfz8SAh954Bi/e2iXutl7g7hqUZqjZzjiLBEJapuhGbItDmC+b6BXyXGKFDKWPdx7rBE8Xo4FZ+1F4gJFC5pVfaTHTCGTCAnvNiQ/8vFIqc8mlQpOQAkw3reQ9lCAKGCc/KkspIE1AQ6HhaaUh4ig1FiRh3OjlH6AoEcTCVhyPXNs+FKir+koZhymCbQOyKvAKJK7SCnPuWOvYClIr2Es9u2mo35nF4obwrfoRq7ijauRadeKFFZ4nilD3LxShIFPe1DTvIvT/FaFPBLYPUD+ykJAse2bn1yMXE6aWQb7i9ZVids9EzynCDtoM35iPz5SOlzsgfSEUAcqAFyazOHU9LLYvlr1ZFt/17bu+/ff0rTdrC9zxqQJ3+hIC94Ce3Ss2rkBnZuMaW5ffHkDJ7/KaiF27np1STPPDPCYCKd3ZeuaNX6bxYWKaW6bv7AjmurZkmii9H/arrTqaFvkdVXQUtNE6WpBMrlQAj+8t48gR6CM+NjNAWxv03bI26jY5ULYSU2D2Dtbtbf1DpbQRbiTDGVV3zXDUvmuqDxTlEMV8ba/mPvveQHs/r0b7d1WRk844+Y1VzfrQvYbN+mtB4V5/kvEu/wA=
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | # sqlite
104 | *.db
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > Remark:
2 | > Although Graphene is pretty cool, do yourself a favor and check the following tutorial instead of this repository. It allows to generate a feature complete GraphQL API from a PostgreSQL database... in minutes (almost). You'll thank me later: [Docker-PostgreSQL-PostGraphile](https://github.com/alexisrolland/docker-postgresql-postgraphile/wiki)
3 |
4 | # Flask-Graphene-SQLAlchemy
5 | The purpose of this repository is to provide a project template to build a **GraphQL API in Python**. Its content has been largely inspired by the references below but code has been modified and enriched to provide a more complete API and a more scalable architecture.
6 |
7 | # Tutorial
8 | The [Github Wiki](https://github.com/alexisrolland/flask-graphene-sqlalchemy/wiki) provides detailed design intentions in a step by step tutorial.
9 |
10 | # References
11 | * [Graphene-SQLAlchemy](http://docs.graphene-python.org/projects/sqlalchemy/en/latest)
12 | * [Flask-Graphene-SQLAlchemy](https://github.com/Getmrahul/Flask-Graphene-SQLAlchemy)
13 | * [Star Wars API](https://swapi.co)
14 |
15 | # Requirements
16 | This project has been developed on **Linux Ubuntu** with **Python 3.5**. It is using the following third party packages. To install them, open a terminal window, change directory to the project folder and execute the following command:
17 |
18 | `$ pip3 install -r requirements.txt`
19 |
20 | The following Python packages will be installed:
21 | * [flask](http://flask.pocoo.org) (0.12.3)
22 | * [flask-graphql](https://pypi.python.org/pypi/Flask-GraphQL) (1.4.1)
23 | * [graphene](http://graphene-python.org) (2.0.0)
24 | * [graphene-sqlalchemy](https://pypi.python.org/pypi/graphene-sqlalchemy/2.0.0) (2.0.0)
25 | * [nose2](http://nose2.readthedocs.io/en/latest/) (0.7.4) - Used for running tests
26 | * [requests](http://docs.python-requests.org/en/master/) (2.20.0) - Used for running tests
27 | * [sqlalchemy](https://www.sqlalchemy.org) (1.3.0)
28 |
29 | # Run Test Cases
30 | To execute all test cases, change directory to the project root folder and execute the following command:
31 |
32 | $ nose2 -v
33 |
--------------------------------------------------------------------------------
/doc/architecture.xml:
--------------------------------------------------------------------------------
1 | 7ZvPc6M2FMf/Gh+bMRIQfMwm2e2hnelMDu2eOjLIhl2MWJATu399JZAwSMIhjiCsW+dg+yE/xPt+9PQzC3i/O3wpUB7/TiKcLsAyOizgwwIAxwU+e+OWY2259Ve1YVskkSh0Mjwl/2BhXArrPolw2SlICUlpkneNIckyHNKODRUFeekW25C0e9ccbbFmeApRqlv/TCIaC6uzXJ4u/IqTbSxuHXjiwhqF37cF2WfifgsAN9WrvrxD0pcoX8YoIi8tE3xcwPuCEFp/2h3uccpjK8NW/+5zz9Wm3gXO6JAfuLD+xTNK91hW2Ue7fAE/ZeuSv+ED+8oCA/yUufwUJc+83vQoYuX/2PPKfqL4QH9BabLNFvCOlUjxhp6usk9b8d5xzpXBhfTN6lm5l2VB506giivmNV+yyy9xQvFTjkJ+9YVxyGwx3aXsm8M+PuOCJkzRu7pKD5TwAqKCD1Xt2O2TNL0nKSmYKSMZrmqUUWli6vnVi9lLWpDv2HQlQmVc1cppKt0WQGjC64MPLZMQ5AsmO0yLIysirkLBhmg7Eq2XE4i+L2xxi0HPFUYk4N82nk8AsA+CATMP8PZVHiJE0RqVXSDM6pb7tWh942jclo+3NI//6Sr3S2eGpA8B20K7XaGha1DaNSgNbCgNTEK/qYWH7MkrXfU2Xvkpc5RJm4TmJlq3QGiXGMgHS5g5/xge0yTjWL0Kybom6rd1DzURwsEmbFEjnsvETRjg9caO+GDpddR3A119xzOoH1gQPzCIrwQaZ9Ed70l5QFJUlknYDWt/o8KHhP7FG/CNJ759Fc351Li5Bxa74tgqyL9+ldci1h+LypCCxmRLMpQ+nqz9EpRkX4TiIYDo3igqtli2HjEU4bc4K1RLCJMO0lbgFNHkuTtyMIkj7vAHSViNGw4c0OUArlZdF/XziF+1u3LVUeB0HS0VUOogaI4qVprHHoTPalR8zmExFC1L+Pg6PrJ3ngk+LrSED5wOHznKH52f2zMAgWkICmafgJo5wE+UgBzZ3Y5NEDhD0O2HETSzHASd4OfLQd40BLXxqXsthZLeHs0WPysDP2BW/KiJQ5vfDOWnWVaRjpZgNH78SfhxPpwfmWtmzI+aNi7mB7oqP95o/JgWWiYZQw/lZxgd8yKhmRwfZSu9NJOojlSkLJIw7mT8TCZptG8rb8gxw/oZ00gXzosOZaTrepeOdP1XHFmkY6K5dj8dzmh0+LOiQx3FXkwHXL3iyB4dQN++2fHNuL9zTPIU3+RHDZb3LLB/0FKp383FzfSivVIKDGCoyf+iZXJ9nCcinKIM0yuJsDbxmjTEegdYbRVcR2jVOcm0odV7jzKM8Q6NlSA8HETuoBgHYA2rPsh+ggCGjbTRQiznR4YQj5MhPibEvXOsSWIMemJ8JcFVc4TjuxMG19WCi/LkSiNr2mQdLbB6vxZTmlcj1R97XFItwHyH/dzQXR5R0U+taFFVTzbskiiqFn1MsnWF7Tv+YEMOZbcK6HKsTAcebKgx6kSqL2bm5RXua9hhks5ai2knQDTemcyhlPUz/9Lll5Xix1H82JtBuW/bSRLN7X1MZNHnhFeoam0lexaqU1eZRbGzhMxEeV+RfnWh8n5w3o9F5fUdoGSXk+I/mJZV8Xw9LRvPoVlIy65pH0U/cPj/YcP3auwpx0qh4VypY1TZisz6dkeJ6T63PczcBCEOh03y14Hnemc3Ot4QXHXjaMrZkauPM8Xs/ltJMrvh3WzAwPBG/tr3LOUnNbzD11DO9GODw6sPHMXM/lrD666G0mshvDINtcK7p0laXukEdNLM4E1zTOr1QwqXH0WY88E5VdrbC8e8jrrBMd50xwOTEPGO7URLx1akxjM+NtfwItOu3Ot9Mz+KI6iO2C7mh309/S9aXfz0D3/w8V8=
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Python CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-python/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers`
11 | - image: circleci/python:3.6.1
12 |
13 | # Specify service dependencies here if necessary
14 | # CircleCI maintains a library of pre-built images
15 | # documented at https://circleci.com/docs/2.0/circleci-images/
16 | # - image: circleci/postgres:9.4
17 |
18 | working_directory: ~/repo
19 |
20 | steps:
21 | - checkout
22 |
23 | # Download and cache dependencies
24 | - restore_cache:
25 | keys:
26 | - v1-dependencies-{{ checksum "requirements.txt" }}
27 | # fallback to using the latest cache if no exact match is found
28 | - v1-dependencies-
29 |
30 | - run:
31 | name: Create Virtual Environment
32 | command: |
33 | python3 -m venv gql
34 |
35 | - run:
36 | name: Install Dependencies
37 | command: |
38 | . gql/bin/activate
39 | pip3 install --upgrade pip
40 | pip3 install -r requirements.txt
41 |
42 | - save_cache:
43 | paths:
44 | - ./gql
45 | key: v1-dependencies-{{ checksum "requirements.txt" }}
46 |
47 | - run:
48 | name: Setup Instance
49 | command: |
50 | . gql/bin/activate
51 | cd example
52 | python3 setup.py
53 |
54 | - run:
55 | name: Start API
56 | command: |
57 | . gql/bin/activate
58 | cd example
59 | python3 api.py
60 | background: true
61 |
62 | - run:
63 | name: Run Tests
64 | command: |
65 | . gql/bin/activate
66 | sleep 10
67 | nose2 --plugin nose2.plugins.junitxml --junit-xml -v
68 |
69 | - store_artifacts:
70 | path: test/junit/test-results.xml
71 | destination: test-results
72 |
73 | - store_test_results:
74 | path: test
75 |
--------------------------------------------------------------------------------
/example/schema_people.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from graphene_sqlalchemy import SQLAlchemyObjectType
3 | from database.base import db_session
4 | from database.model_people import ModelPeople
5 | import graphene
6 | import utils
7 |
8 |
9 | # Create a generic class to mutualize description of people attributes for both queries and mutations
10 | class PeopleAttribute:
11 | name = graphene.String(description="Name of the person.")
12 | height = graphene.String(description="Height of the person.")
13 | mass = graphene.String(description="Mass of the person.")
14 | hair_color = graphene.String(description="Hair color of the person.")
15 | skin_color = graphene.String(description="Skin color of the person.")
16 | eye_color = graphene.String(description="Eye color of the person.")
17 | birth_year = graphene.String(description="Birth year of the person.")
18 | gender = graphene.String(description="Gender of the person.")
19 | planet_id = graphene.ID(description="Global Id of the planet from which the person comes from.")
20 | url = graphene.String(description="URL of the person in the Star Wars API.")
21 |
22 |
23 | class People(SQLAlchemyObjectType):
24 | """People node."""
25 |
26 | class Meta:
27 | model = ModelPeople
28 | interfaces = (graphene.relay.Node,)
29 |
30 |
31 | class CreatePersonInput(graphene.InputObjectType, PeopleAttribute):
32 | """Arguments to create a person."""
33 | pass
34 |
35 |
36 | class CreatePerson(graphene.Mutation):
37 | """Mutation to create a person."""
38 | person = graphene.Field(lambda: People, description="Person created by this mutation.")
39 |
40 | class Arguments:
41 | input = CreatePersonInput(required=True)
42 |
43 | def mutate(self, info, input):
44 | data = utils.input_to_dictionary(input)
45 | data['created'] = datetime.utcnow()
46 | data['edited'] = datetime.utcnow()
47 |
48 | person = ModelPeople(**data)
49 | db_session.add(person)
50 | db_session.commit()
51 |
52 | return CreatePerson(person=person)
53 |
54 |
55 | class UpdatePersonInput(graphene.InputObjectType, PeopleAttribute):
56 | """Arguments to update a person."""
57 | id = graphene.ID(required=True, description="Global Id of the person.")
58 |
59 |
60 | class UpdatePerson(graphene.Mutation):
61 | """Update a person."""
62 | person = graphene.Field(lambda: People, description="Person updated by this mutation.")
63 |
64 | class Arguments:
65 | input = UpdatePersonInput(required=True)
66 |
67 | def mutate(self, info, input):
68 | data = utils.input_to_dictionary(input)
69 | data['edited'] = datetime.utcnow()
70 |
71 | person = db_session.query(ModelPeople).filter_by(id=data['id'])
72 | person.update(data)
73 | db_session.commit()
74 | person = db_session.query(ModelPeople).filter_by(id=data['id']).first()
75 |
76 | return UpdatePerson(person=person)
77 |
--------------------------------------------------------------------------------
/example/schema_planet.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from graphene_sqlalchemy import SQLAlchemyObjectType
3 | from database.base import db_session
4 | from database.model_planet import ModelPlanet
5 | import graphene
6 | import utils
7 |
8 |
9 | # Create a generic class to mutualize description of planet attributes for both queries and mutations
10 | class PlanetAttribute:
11 | name = graphene.String(description="Name of the planet.")
12 | rotation_period = graphene.String(description="Rotation period of the planet.")
13 | orbital_period = graphene.String(description="Orbital period of the planet.")
14 | diameter = graphene.String(description="Diameter of the planet.")
15 | climate = graphene.String(description="Climate period of the planet.")
16 | gravity = graphene.String(description="Gravity of the planet.")
17 | terrain = graphene.String(description="Terrain of the planet.")
18 | surface_water = graphene.String(description="Surface water of the planet.")
19 | population = graphene.String(description="Population of the planet.")
20 | url = graphene.String(description="URL of the planet in the Star Wars API.")
21 |
22 |
23 | class Planet(SQLAlchemyObjectType):
24 | """Planet node."""
25 |
26 | class Meta:
27 | model = ModelPlanet
28 | interfaces = (graphene.relay.Node,)
29 |
30 |
31 | class CreatePlanetInput(graphene.InputObjectType, PlanetAttribute):
32 | """Arguments to create a planet."""
33 | pass
34 |
35 |
36 | class CreatePlanet(graphene.Mutation):
37 | """Create a planet."""
38 | planet = graphene.Field(lambda: Planet, description="Planet created by this mutation.")
39 |
40 | class Arguments:
41 | input = CreatePlanetInput(required=True)
42 |
43 | def mutate(self, info, input):
44 | data = utils.input_to_dictionary(input)
45 | data['created'] = datetime.utcnow()
46 | data['edited'] = datetime.utcnow()
47 |
48 | planet = ModelPlanet(**data)
49 | db_session.add(planet)
50 | db_session.commit()
51 |
52 | return CreatePlanet(planet=planet)
53 |
54 |
55 | class UpdatePlanetInput(graphene.InputObjectType, PlanetAttribute):
56 | """Arguments to update a planet."""
57 | id = graphene.ID(required=True, description="Global Id of the planet.")
58 |
59 |
60 | class UpdatePlanet(graphene.Mutation):
61 | """Update a planet."""
62 | planet = graphene.Field(lambda: Planet, description="Planet updated by this mutation.")
63 |
64 | class Arguments:
65 | input = UpdatePlanetInput(required=True)
66 |
67 | def mutate(self, info, input):
68 | data = utils.input_to_dictionary(input)
69 | data['edited'] = datetime.utcnow()
70 |
71 | planet = db_session.query(ModelPlanet).filter_by(id=data['id'])
72 | planet.update(data)
73 | db_session.commit()
74 | planet = db_session.query(ModelPlanet).filter_by(id=data['id']).first()
75 |
76 | return UpdatePlanet(planet=planet)
77 |
--------------------------------------------------------------------------------
/test/test_api.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Unit test for api module GraphQL queries."""
3 | import requests
4 | import unittest
5 |
6 |
7 | class TestApi(unittest.TestCase):
8 | """Class to execute unit tests for api.py."""
9 |
10 | @classmethod
11 | def setUpClass(self):
12 | """Set up function called when class is consructed."""
13 | self.base_url = 'http://127.0.0.1:5000/graphql'
14 | self.headers = {'content-type': 'application/json'}
15 |
16 | def test_query_people(self):
17 | # Get batch list
18 | payload = '{"query": "{people(id:\\"UGVvcGxlOjE=\\"){name}}"}'
19 | response = requests.post(self.base_url, headers=self.headers, data=payload)
20 | json = response.json()
21 |
22 | self.assertEqual(response.status_code, 200)
23 | self.assertEqual(json['data']['people']['name'], 'Luke Skywalker')
24 |
25 | def test_query_people_list(self):
26 | # Get batch list
27 | payload = '{"query": "{peopleList{edges{node{id}}}}"}'
28 | response = requests.post(self.base_url, headers=self.headers, data=payload)
29 | json = response.json()
30 |
31 | self.assertEqual(response.status_code, 200)
32 | self.assertGreater(len(json['data']['peopleList']['edges']), 0)
33 |
34 | def test_query_planet(self):
35 | # Get batch list
36 | payload = '{"query": "{planet(id:\\"UGxhbmV0OjE=\\"){name}}"}'
37 | response = requests.post(self.base_url, headers=self.headers, data=payload)
38 | json = response.json()
39 |
40 | self.assertEqual(response.status_code, 200)
41 | self.assertEqual(json['data']['planet']['name'], 'Tatooine')
42 |
43 | def test_query_planet_list(self):
44 | # Get batch list
45 | payload = '{"query": "{planetList{edges{node{id}}}}"}'
46 | response = requests.post(self.base_url, headers=self.headers, data=payload)
47 | json = response.json()
48 |
49 | self.assertEqual(response.status_code, 200)
50 | self.assertGreater(len(json['data']['planetList']['edges']), 0)
51 |
52 | def test_create_person(self):
53 | # Get batch list
54 | payload = '{"query": "mutation{createPerson(input:{name:\\"Alexis ROLLAND\\"}){person{name}}}"}'
55 | response = requests.post(self.base_url, headers=self.headers, data=payload)
56 | json = response.json()
57 |
58 | self.assertEqual(response.status_code, 200)
59 | self.assertEqual(json['data']['createPerson']['person']['name'], 'Alexis ROLLAND')
60 |
61 | def test_update_person(self):
62 | # Get batch list
63 | payload = '{"query": "mutation{updatePerson(input:{id:\\"UGVvcGxlOjI=\\",height:\\"170\\"}){person{height}}}"}'
64 | response = requests.post(self.base_url, headers=self.headers, data=payload)
65 | json = response.json()
66 |
67 | self.assertEqual(response.status_code, 200)
68 | self.assertEqual(json['data']['updatePerson']['person']['height'], '170')
69 |
70 | def test_create_planet(self):
71 | # Get batch list
72 | payload = '{"query": "mutation{createPlanet(input:{name:\\"Earth\\"}){planet{name}}}"}'
73 | response = requests.post(self.base_url, headers=self.headers, data=payload)
74 | json = response.json()
75 |
76 | self.assertEqual(response.status_code, 200)
77 | self.assertEqual(json['data']['createPlanet']['planet']['name'], 'Earth')
78 |
79 | def test_update_planet(self):
80 | # Get batch list
81 | payload = '{"query": "mutation{updatePlanet(input:{id:\\"UGxhbmV0OjE=\\",rotationPeriod:\\"24\\"}){planet{rotationPeriod}}}"}'
82 | response = requests.post(self.base_url, headers=self.headers, data=payload)
83 | json = response.json()
84 |
85 | self.assertEqual(response.status_code, 200)
86 | self.assertEqual(json['data']['updatePlanet']['planet']['rotationPeriod'], '24')
87 |
88 | @classmethod
89 | def tearDownClass(self):
90 | """Tear down function called when class is deconstructed."""
91 | pass
92 |
93 |
94 | if __name__ == '__main__':
95 | # Test api endpoints
96 | suite = unittest.TestLoader().loadTestsFromTestCase(TestApi)
97 | unittest.TextTestRunner(verbosity=2).run(suite)
98 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/example/database/data/planet.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Alderaan",
4 | "rotation_period": "24",
5 | "orbital_period": "364",
6 | "diameter": "12500",
7 | "climate": "temperate",
8 | "gravity": "1 standard",
9 | "terrain": "grasslands, mountains",
10 | "surface_water": "40",
11 | "population": "2000000000",
12 | "created": "2014-12-10 11:35:48.479000Z",
13 | "edited": "2014-12-20 20:58:18.420000Z",
14 | "url": "https://swapi.co/api/planets/2/",
15 | "id": "2"
16 | },
17 | {
18 | "name": "Yavin IV",
19 | "rotation_period": "24",
20 | "orbital_period": "4818",
21 | "diameter": "10200",
22 | "climate": "temperate, tropical",
23 | "gravity": "1 standard",
24 | "terrain": "jungle, rainforests",
25 | "surface_water": "8",
26 | "population": "1000",
27 | "created": "2014-12-10 11:37:19.144000Z",
28 | "edited": "2014-12-20 20:58:18.421000Z",
29 | "url": "https://swapi.co/api/planets/3/",
30 | "id": "3"
31 | },
32 | {
33 | "name": "Hoth",
34 | "rotation_period": "23",
35 | "orbital_period": "549",
36 | "diameter": "7200",
37 | "climate": "frozen",
38 | "gravity": "1.1 standard",
39 | "terrain": "tundra, ice caves, mountain ranges",
40 | "surface_water": "100",
41 | "population": "unknown",
42 | "created": "2014-12-10 11:39:13.934000Z",
43 | "edited": "2014-12-20 20:58:18.423000Z",
44 | "url": "https://swapi.co/api/planets/4/",
45 | "id": "4"
46 | },
47 | {
48 | "name": "Dagobah",
49 | "rotation_period": "23",
50 | "orbital_period": "341",
51 | "diameter": "8900",
52 | "climate": "murky",
53 | "gravity": "N/A",
54 | "terrain": "swamp, jungles",
55 | "surface_water": "8",
56 | "population": "unknown",
57 | "created": "2014-12-10 11:42:22.590000Z",
58 | "edited": "2014-12-20 20:58:18.425000Z",
59 | "url": "https://swapi.co/api/planets/5/",
60 | "id": "5"
61 | },
62 | {
63 | "name": "Bespin",
64 | "rotation_period": "12",
65 | "orbital_period": "5110",
66 | "diameter": "118000",
67 | "climate": "temperate",
68 | "gravity": "1.5 (surface), 1 standard (Cloud City)",
69 | "terrain": "gas giant",
70 | "surface_water": "0",
71 | "population": "6000000",
72 | "created": "2014-12-10 11:43:55.240000Z",
73 | "edited": "2014-12-20 20:58:18.427000Z",
74 | "url": "https://swapi.co/api/planets/6/",
75 | "id": "6"
76 | },
77 | {
78 | "name": "Endor",
79 | "rotation_period": "18",
80 | "orbital_period": "402",
81 | "diameter": "4900",
82 | "climate": "temperate",
83 | "gravity": "0.85 standard",
84 | "terrain": "forests, mountains, lakes",
85 | "surface_water": "8",
86 | "population": "30000000",
87 | "created": "2014-12-10 11:50:29.349000Z",
88 | "edited": "2014-12-20 20:58:18.429000Z",
89 | "url": "https://swapi.co/api/planets/7/",
90 | "id": "7"
91 | },
92 | {
93 | "name": "Naboo",
94 | "rotation_period": "26",
95 | "orbital_period": "312",
96 | "diameter": "12120",
97 | "climate": "temperate",
98 | "gravity": "1 standard",
99 | "terrain": "grassy hills, swamps, forests, mountains",
100 | "surface_water": "12",
101 | "population": "4500000000",
102 | "created": "2014-12-10 11:52:31.066000Z",
103 | "edited": "2014-12-20 20:58:18.430000Z",
104 | "url": "https://swapi.co/api/planets/8/",
105 | "id": "8"
106 | },
107 | {
108 | "name": "Coruscant",
109 | "rotation_period": "24",
110 | "orbital_period": "368",
111 | "diameter": "12240",
112 | "climate": "temperate",
113 | "gravity": "1 standard",
114 | "terrain": "cityscape, mountains",
115 | "surface_water": "unknown",
116 | "population": "1000000000000",
117 | "created": "2014-12-10 11:54:13.921000Z",
118 | "edited": "2014-12-20 20:58:18.432000Z",
119 | "url": "https://swapi.co/api/planets/9/",
120 | "id": "9"
121 | },
122 | {
123 | "name": "Kamino",
124 | "rotation_period": "27",
125 | "orbital_period": "463",
126 | "diameter": "19720",
127 | "climate": "temperate",
128 | "gravity": "1 standard",
129 | "terrain": "ocean",
130 | "surface_water": "100",
131 | "population": "1000000000",
132 | "created": "2014-12-10 12:45:06.577000Z",
133 | "edited": "2014-12-20 20:58:18.434000Z",
134 | "url": "https://swapi.co/api/planets/10/",
135 | "id": "10"
136 | },
137 | {
138 | "name": "Geonosis",
139 | "rotation_period": "30",
140 | "orbital_period": "256",
141 | "diameter": "11370",
142 | "climate": "temperate, arid",
143 | "gravity": "0.9 standard",
144 | "terrain": "rock, desert, mountain, barren",
145 | "surface_water": "5",
146 | "population": "100000000000",
147 | "created": "2014-12-10 12:47:22.350000Z",
148 | "edited": "2014-12-20 20:58:18.437000Z",
149 | "url": "https://swapi.co/api/planets/11/",
150 | "id": "11"
151 | },
152 | {
153 | "name": "Utapau",
154 | "rotation_period": "27",
155 | "orbital_period": "351",
156 | "diameter": "12900",
157 | "climate": "temperate, arid, windy",
158 | "gravity": "1 standard",
159 | "terrain": "scrublands, savanna, canyons, sinkholes",
160 | "surface_water": "0.9",
161 | "population": "95000000",
162 | "created": "2014-12-10 12:49:01.491000Z",
163 | "edited": "2014-12-20 20:58:18.439000Z",
164 | "url": "https://swapi.co/api/planets/12/",
165 | "id": "12"
166 | },
167 | {
168 | "name": "Mustafar",
169 | "rotation_period": "36",
170 | "orbital_period": "412",
171 | "diameter": "4200",
172 | "climate": "hot",
173 | "gravity": "1 standard",
174 | "terrain": "volcanoes, lava rivers, mountains, caves",
175 | "surface_water": "0",
176 | "population": "20000",
177 | "created": "2014-12-10 12:50:16.526000Z",
178 | "edited": "2014-12-20 20:58:18.440000Z",
179 | "url": "https://swapi.co/api/planets/13/",
180 | "id": "13"
181 | },
182 | {
183 | "name": "Kashyyyk",
184 | "rotation_period": "26",
185 | "orbital_period": "381",
186 | "diameter": "12765",
187 | "climate": "tropical",
188 | "gravity": "1 standard",
189 | "terrain": "jungle, forests, lakes, rivers",
190 | "surface_water": "60",
191 | "population": "45000000",
192 | "created": "2014-12-10 13:32:00.124000Z",
193 | "edited": "2014-12-20 20:58:18.442000Z",
194 | "url": "https://swapi.co/api/planets/14/",
195 | "id": "14"
196 | },
197 | {
198 | "name": "Polis Massa",
199 | "rotation_period": "24",
200 | "orbital_period": "590",
201 | "diameter": "0",
202 | "climate": "artificial temperate ",
203 | "gravity": "0.56 standard",
204 | "terrain": "airless asteroid",
205 | "surface_water": "0",
206 | "population": "1000000",
207 | "created": "2014-12-10 13:33:46.405000Z",
208 | "edited": "2014-12-20 20:58:18.444000Z",
209 | "url": "https://swapi.co/api/planets/15/",
210 | "id": "15"
211 | },
212 | {
213 | "name": "Mygeeto",
214 | "rotation_period": "12",
215 | "orbital_period": "167",
216 | "diameter": "10088",
217 | "climate": "frigid",
218 | "gravity": "1 standard",
219 | "terrain": "glaciers, mountains, ice canyons",
220 | "surface_water": "unknown",
221 | "population": "19000000",
222 | "created": "2014-12-10 13:43:39.139000Z",
223 | "edited": "2014-12-20 20:58:18.446000Z",
224 | "url": "https://swapi.co/api/planets/16/",
225 | "id": "16"
226 | },
227 | {
228 | "name": "Felucia",
229 | "rotation_period": "34",
230 | "orbital_period": "231",
231 | "diameter": "9100",
232 | "climate": "hot, humid",
233 | "gravity": "0.75 standard",
234 | "terrain": "fungus forests",
235 | "surface_water": "unknown",
236 | "population": "8500000",
237 | "created": "2014-12-10 13:44:50.397000Z",
238 | "edited": "2014-12-20 20:58:18.447000Z",
239 | "url": "https://swapi.co/api/planets/17/",
240 | "id": "17"
241 | },
242 | {
243 | "name": "Cato Neimoidia",
244 | "rotation_period": "25",
245 | "orbital_period": "278",
246 | "diameter": "0",
247 | "climate": "temperate, moist",
248 | "gravity": "1 standard",
249 | "terrain": "mountains, fields, forests, rock arches",
250 | "surface_water": "unknown",
251 | "population": "10000000",
252 | "created": "2014-12-10 13:46:28.704000Z",
253 | "edited": "2014-12-20 20:58:18.449000Z",
254 | "url": "https://swapi.co/api/planets/18/",
255 | "id": "18"
256 | },
257 | {
258 | "name": "Saleucami",
259 | "rotation_period": "26",
260 | "orbital_period": "392",
261 | "diameter": "14920",
262 | "climate": "hot",
263 | "gravity": "unknown",
264 | "terrain": "caves, desert, mountains, volcanoes",
265 | "surface_water": "unknown",
266 | "population": "1400000000",
267 | "created": "2014-12-10 13:47:46.874000Z",
268 | "edited": "2014-12-20 20:58:18.450000Z",
269 | "url": "https://swapi.co/api/planets/19/",
270 | "id": "19"
271 | },
272 | {
273 | "name": "Stewjon",
274 | "rotation_period": "unknown",
275 | "orbital_period": "unknown",
276 | "diameter": "0",
277 | "climate": "temperate",
278 | "gravity": "1 standard",
279 | "terrain": "grass",
280 | "surface_water": "unknown",
281 | "population": "unknown",
282 | "created": "2014-12-10 16:16:26.566000Z",
283 | "edited": "2014-12-20 20:58:18.452000Z",
284 | "url": "https://swapi.co/api/planets/20/",
285 | "id": "20"
286 | },
287 | {
288 | "name": "Eriadu",
289 | "rotation_period": "24",
290 | "orbital_period": "360",
291 | "diameter": "13490",
292 | "climate": "polluted",
293 | "gravity": "1 standard",
294 | "terrain": "cityscape",
295 | "surface_water": "unknown",
296 | "population": "22000000000",
297 | "created": "2014-12-10 16:26:54.384000Z",
298 | "edited": "2014-12-20 20:58:18.454000Z",
299 | "url": "https://swapi.co/api/planets/21/",
300 | "id": "21"
301 | },
302 | {
303 | "name": "Corellia",
304 | "rotation_period": "25",
305 | "orbital_period": "329",
306 | "diameter": "11000",
307 | "climate": "temperate",
308 | "gravity": "1 standard",
309 | "terrain": "plains, urban, hills, forests",
310 | "surface_water": "70",
311 | "population": "3000000000",
312 | "created": "2014-12-10 16:49:12.453000Z",
313 | "edited": "2014-12-20 20:58:18.456000Z",
314 | "url": "https://swapi.co/api/planets/22/",
315 | "id": "22"
316 | },
317 | {
318 | "name": "Rodia",
319 | "rotation_period": "29",
320 | "orbital_period": "305",
321 | "diameter": "7549",
322 | "climate": "hot",
323 | "gravity": "1 standard",
324 | "terrain": "jungles, oceans, urban, swamps",
325 | "surface_water": "60",
326 | "population": "1300000000",
327 | "created": "2014-12-10 17:03:28.110000Z",
328 | "edited": "2014-12-20 20:58:18.458000Z",
329 | "url": "https://swapi.co/api/planets/23/",
330 | "id": "23"
331 | },
332 | {
333 | "name": "Nal Hutta",
334 | "rotation_period": "87",
335 | "orbital_period": "413",
336 | "diameter": "12150",
337 | "climate": "temperate",
338 | "gravity": "1 standard",
339 | "terrain": "urban, oceans, swamps, bogs",
340 | "surface_water": "unknown",
341 | "population": "7000000000",
342 | "created": "2014-12-10 17:11:29.452000Z",
343 | "edited": "2014-12-20 20:58:18.460000Z",
344 | "url": "https://swapi.co/api/planets/24/",
345 | "id": "24"
346 | },
347 | {
348 | "name": "Dantooine",
349 | "rotation_period": "25",
350 | "orbital_period": "378",
351 | "diameter": "9830",
352 | "climate": "temperate",
353 | "gravity": "1 standard",
354 | "terrain": "oceans, savannas, mountains, grasslands",
355 | "surface_water": "unknown",
356 | "population": "1000",
357 | "created": "2014-12-10 17:23:29.896000Z",
358 | "edited": "2014-12-20 20:58:18.461000Z",
359 | "url": "https://swapi.co/api/planets/25/",
360 | "id": "25"
361 | },
362 | {
363 | "name": "Bestine IV",
364 | "rotation_period": "26",
365 | "orbital_period": "680",
366 | "diameter": "6400",
367 | "climate": "temperate",
368 | "gravity": "unknown",
369 | "terrain": "rocky islands, oceans",
370 | "surface_water": "98",
371 | "population": "62000000",
372 | "created": "2014-12-12 11:16:55.078000Z",
373 | "edited": "2014-12-20 20:58:18.463000Z",
374 | "url": "https://swapi.co/api/planets/26/",
375 | "id": "26"
376 | },
377 | {
378 | "name": "Ord Mantell",
379 | "rotation_period": "26",
380 | "orbital_period": "334",
381 | "diameter": "14050",
382 | "climate": "temperate",
383 | "gravity": "1 standard",
384 | "terrain": "plains, seas, mesas",
385 | "surface_water": "10",
386 | "population": "4000000000",
387 | "created": "2014-12-15 12:23:41.661000Z",
388 | "edited": "2014-12-20 20:58:18.464000Z",
389 | "url": "https://swapi.co/api/planets/27/",
390 | "id": "27"
391 | },
392 | {
393 | "name": "unknown",
394 | "rotation_period": "0",
395 | "orbital_period": "0",
396 | "diameter": "0",
397 | "climate": "unknown",
398 | "gravity": "unknown",
399 | "terrain": "unknown",
400 | "surface_water": "unknown",
401 | "population": "unknown",
402 | "created": "2014-12-15 12:25:59.569000Z",
403 | "edited": "2014-12-20 20:58:18.466000Z",
404 | "url": "https://swapi.co/api/planets/28/",
405 | "id": "28"
406 | },
407 | {
408 | "name": "Trandosha",
409 | "rotation_period": "25",
410 | "orbital_period": "371",
411 | "diameter": "0",
412 | "climate": "arid",
413 | "gravity": "0.62 standard",
414 | "terrain": "mountains, seas, grasslands, deserts",
415 | "surface_water": "unknown",
416 | "population": "42000000",
417 | "created": "2014-12-15 12:53:47.695000Z",
418 | "edited": "2014-12-20 20:58:18.468000Z",
419 | "url": "https://swapi.co/api/planets/29/",
420 | "id": "29"
421 | },
422 | {
423 | "name": "Socorro",
424 | "rotation_period": "20",
425 | "orbital_period": "326",
426 | "diameter": "0",
427 | "climate": "arid",
428 | "gravity": "1 standard",
429 | "terrain": "deserts, mountains",
430 | "surface_water": "unknown",
431 | "population": "300000000",
432 | "created": "2014-12-15 12:56:31.121000Z",
433 | "edited": "2014-12-20 20:58:18.469000Z",
434 | "url": "https://swapi.co/api/planets/30/",
435 | "id": "30"
436 | },
437 | {
438 | "name": "Mon Cala",
439 | "rotation_period": "21",
440 | "orbital_period": "398",
441 | "diameter": "11030",
442 | "climate": "temperate",
443 | "gravity": "1",
444 | "terrain": "oceans, reefs, islands",
445 | "surface_water": "100",
446 | "population": "27000000000",
447 | "created": "2014-12-18 11:07:01.792000Z",
448 | "edited": "2014-12-20 20:58:18.471000Z",
449 | "url": "https://swapi.co/api/planets/31/",
450 | "id": "31"
451 | },
452 | {
453 | "name": "Chandrila",
454 | "rotation_period": "20",
455 | "orbital_period": "368",
456 | "diameter": "13500",
457 | "climate": "temperate",
458 | "gravity": "1",
459 | "terrain": "plains, forests",
460 | "surface_water": "40",
461 | "population": "1200000000",
462 | "created": "2014-12-18 11:11:51.872000Z",
463 | "edited": "2014-12-20 20:58:18.472000Z",
464 | "url": "https://swapi.co/api/planets/32/",
465 | "id": "32"
466 | },
467 | {
468 | "name": "Sullust",
469 | "rotation_period": "20",
470 | "orbital_period": "263",
471 | "diameter": "12780",
472 | "climate": "superheated",
473 | "gravity": "1",
474 | "terrain": "mountains, volcanoes, rocky deserts",
475 | "surface_water": "5",
476 | "population": "18500000000",
477 | "created": "2014-12-18 11:25:40.243000Z",
478 | "edited": "2014-12-20 20:58:18.474000Z",
479 | "url": "https://swapi.co/api/planets/33/",
480 | "id": "33"
481 | },
482 | {
483 | "name": "Toydaria",
484 | "rotation_period": "21",
485 | "orbital_period": "184",
486 | "diameter": "7900",
487 | "climate": "temperate",
488 | "gravity": "1",
489 | "terrain": "swamps, lakes",
490 | "surface_water": "unknown",
491 | "population": "11000000",
492 | "created": "2014-12-19 17:47:54.403000Z",
493 | "edited": "2014-12-20 20:58:18.476000Z",
494 | "url": "https://swapi.co/api/planets/34/",
495 | "id": "34"
496 | },
497 | {
498 | "name": "Malastare",
499 | "rotation_period": "26",
500 | "orbital_period": "201",
501 | "diameter": "18880",
502 | "climate": "arid, temperate, tropical",
503 | "gravity": "1.56",
504 | "terrain": "swamps, deserts, jungles, mountains",
505 | "surface_water": "unknown",
506 | "population": "2000000000",
507 | "created": "2014-12-19 17:52:13.106000Z",
508 | "edited": "2014-12-20 20:58:18.478000Z",
509 | "url": "https://swapi.co/api/planets/35/",
510 | "id": "35"
511 | },
512 | {
513 | "name": "Dathomir",
514 | "rotation_period": "24",
515 | "orbital_period": "491",
516 | "diameter": "10480",
517 | "climate": "temperate",
518 | "gravity": "0.9",
519 | "terrain": "forests, deserts, savannas",
520 | "surface_water": "unknown",
521 | "population": "5200",
522 | "created": "2014-12-19 18:00:40.142000Z",
523 | "edited": "2014-12-20 20:58:18.480000Z",
524 | "url": "https://swapi.co/api/planets/36/",
525 | "id": "36"
526 | },
527 | {
528 | "name": "Ryloth",
529 | "rotation_period": "30",
530 | "orbital_period": "305",
531 | "diameter": "10600",
532 | "climate": "temperate, arid, subartic",
533 | "gravity": "1",
534 | "terrain": "mountains, valleys, deserts, tundra",
535 | "surface_water": "5",
536 | "population": "1500000000",
537 | "created": "2014-12-20 09:46:25.740000Z",
538 | "edited": "2014-12-20 20:58:18.481000Z",
539 | "url": "https://swapi.co/api/planets/37/",
540 | "id": "37"
541 | },
542 | {
543 | "name": "Aleen Minor",
544 | "rotation_period": "unknown",
545 | "orbital_period": "unknown",
546 | "diameter": "unknown",
547 | "climate": "unknown",
548 | "gravity": "unknown",
549 | "terrain": "unknown",
550 | "surface_water": "unknown",
551 | "population": "unknown",
552 | "created": "2014-12-20 09:52:23.452000Z",
553 | "edited": "2014-12-20 20:58:18.483000Z",
554 | "url": "https://swapi.co/api/planets/38/",
555 | "id": "38"
556 | },
557 | {
558 | "name": "Vulpter",
559 | "rotation_period": "22",
560 | "orbital_period": "391",
561 | "diameter": "14900",
562 | "climate": "temperate, artic",
563 | "gravity": "1",
564 | "terrain": "urban, barren",
565 | "surface_water": "unknown",
566 | "population": "421000000",
567 | "created": "2014-12-20 09:56:58.874000Z",
568 | "edited": "2014-12-20 20:58:18.485000Z",
569 | "url": "https://swapi.co/api/planets/39/",
570 | "id": "39"
571 | },
572 | {
573 | "name": "Troiken",
574 | "rotation_period": "unknown",
575 | "orbital_period": "unknown",
576 | "diameter": "unknown",
577 | "climate": "unknown",
578 | "gravity": "unknown",
579 | "terrain": "desert, tundra, rainforests, mountains",
580 | "surface_water": "unknown",
581 | "population": "unknown",
582 | "created": "2014-12-20 10:01:37.395000Z",
583 | "edited": "2014-12-20 20:58:18.487000Z",
584 | "url": "https://swapi.co/api/planets/40/",
585 | "id": "40"
586 | },
587 | {
588 | "name": "Tund",
589 | "rotation_period": "48",
590 | "orbital_period": "1770",
591 | "diameter": "12190",
592 | "climate": "unknown",
593 | "gravity": "unknown",
594 | "terrain": "barren, ash",
595 | "surface_water": "unknown",
596 | "population": "0",
597 | "created": "2014-12-20 10:07:29.578000Z",
598 | "edited": "2014-12-20 20:58:18.489000Z",
599 | "url": "https://swapi.co/api/planets/41/",
600 | "id": "41"
601 | },
602 | {
603 | "name": "Haruun Kal",
604 | "rotation_period": "25",
605 | "orbital_period": "383",
606 | "diameter": "10120",
607 | "climate": "temperate",
608 | "gravity": "0.98",
609 | "terrain": "toxic cloudsea, plateaus, volcanoes",
610 | "surface_water": "unknown",
611 | "population": "705300",
612 | "created": "2014-12-20 10:12:28.980000Z",
613 | "edited": "2014-12-20 20:58:18.491000Z",
614 | "url": "https://swapi.co/api/planets/42/",
615 | "id": "42"
616 | },
617 | {
618 | "name": "Cerea",
619 | "rotation_period": "27",
620 | "orbital_period": "386",
621 | "diameter": "unknown",
622 | "climate": "temperate",
623 | "gravity": "1",
624 | "terrain": "verdant",
625 | "surface_water": "20",
626 | "population": "450000000",
627 | "created": "2014-12-20 10:14:48.178000Z",
628 | "edited": "2014-12-20 20:58:18.493000Z",
629 | "url": "https://swapi.co/api/planets/43/",
630 | "id": "43"
631 | },
632 | {
633 | "name": "Glee Anselm",
634 | "rotation_period": "33",
635 | "orbital_period": "206",
636 | "diameter": "15600",
637 | "climate": "tropical, temperate",
638 | "gravity": "1",
639 | "terrain": "lakes, islands, swamps, seas",
640 | "surface_water": "80",
641 | "population": "500000000",
642 | "created": "2014-12-20 10:18:26.110000Z",
643 | "edited": "2014-12-20 20:58:18.495000Z",
644 | "url": "https://swapi.co/api/planets/44/",
645 | "id": "44"
646 | },
647 | {
648 | "name": "Iridonia",
649 | "rotation_period": "29",
650 | "orbital_period": "413",
651 | "diameter": "unknown",
652 | "climate": "unknown",
653 | "gravity": "unknown",
654 | "terrain": "rocky canyons, acid pools",
655 | "surface_water": "unknown",
656 | "population": "unknown",
657 | "created": "2014-12-20 10:26:05.788000Z",
658 | "edited": "2014-12-20 20:58:18.497000Z",
659 | "url": "https://swapi.co/api/planets/45/",
660 | "id": "45"
661 | },
662 | {
663 | "name": "Tholoth",
664 | "rotation_period": "unknown",
665 | "orbital_period": "unknown",
666 | "diameter": "unknown",
667 | "climate": "unknown",
668 | "gravity": "unknown",
669 | "terrain": "unknown",
670 | "surface_water": "unknown",
671 | "population": "unknown",
672 | "created": "2014-12-20 10:28:31.117000Z",
673 | "edited": "2014-12-20 20:58:18.498000Z",
674 | "url": "https://swapi.co/api/planets/46/",
675 | "id": "46"
676 | },
677 | {
678 | "name": "Iktotch",
679 | "rotation_period": "22",
680 | "orbital_period": "481",
681 | "diameter": "unknown",
682 | "climate": "arid, rocky, windy",
683 | "gravity": "1",
684 | "terrain": "rocky",
685 | "surface_water": "unknown",
686 | "population": "unknown",
687 | "created": "2014-12-20 10:31:32.413000Z",
688 | "edited": "2014-12-20 20:58:18.500000Z",
689 | "url": "https://swapi.co/api/planets/47/",
690 | "id": "47"
691 | },
692 | {
693 | "name": "Quermia",
694 | "rotation_period": "unknown",
695 | "orbital_period": "unknown",
696 | "diameter": "unknown",
697 | "climate": "unknown",
698 | "gravity": "unknown",
699 | "terrain": "unknown",
700 | "surface_water": "unknown",
701 | "population": "unknown",
702 | "created": "2014-12-20 10:34:08.249000Z",
703 | "edited": "2014-12-20 20:58:18.502000Z",
704 | "url": "https://swapi.co/api/planets/48/",
705 | "id": "48"
706 | },
707 | {
708 | "name": "Dorin",
709 | "rotation_period": "22",
710 | "orbital_period": "409",
711 | "diameter": "13400",
712 | "climate": "temperate",
713 | "gravity": "1",
714 | "terrain": "unknown",
715 | "surface_water": "unknown",
716 | "population": "unknown",
717 | "created": "2014-12-20 10:48:36.141000Z",
718 | "edited": "2014-12-20 20:58:18.504000Z",
719 | "url": "https://swapi.co/api/planets/49/",
720 | "id": "49"
721 | },
722 | {
723 | "name": "Champala",
724 | "rotation_period": "27",
725 | "orbital_period": "318",
726 | "diameter": "unknown",
727 | "climate": "temperate",
728 | "gravity": "1",
729 | "terrain": "oceans, rainforests, plateaus",
730 | "surface_water": "unknown",
731 | "population": "3500000000",
732 | "created": "2014-12-20 10:52:51.524000Z",
733 | "edited": "2014-12-20 20:58:18.506000Z",
734 | "url": "https://swapi.co/api/planets/50/",
735 | "id": "50"
736 | },
737 | {
738 | "name": "Mirial",
739 | "rotation_period": "unknown",
740 | "orbital_period": "unknown",
741 | "diameter": "unknown",
742 | "climate": "unknown",
743 | "gravity": "unknown",
744 | "terrain": "deserts",
745 | "surface_water": "unknown",
746 | "population": "unknown",
747 | "created": "2014-12-20 16:44:46.318000Z",
748 | "edited": "2014-12-20 20:58:18.508000Z",
749 | "url": "https://swapi.co/api/planets/51/",
750 | "id": "51"
751 | },
752 | {
753 | "name": "Serenno",
754 | "rotation_period": "unknown",
755 | "orbital_period": "unknown",
756 | "diameter": "unknown",
757 | "climate": "unknown",
758 | "gravity": "unknown",
759 | "terrain": "rainforests, rivers, mountains",
760 | "surface_water": "unknown",
761 | "population": "unknown",
762 | "created": "2014-12-20 16:52:13.357000Z",
763 | "edited": "2014-12-20 20:58:18.510000Z",
764 | "url": "https://swapi.co/api/planets/52/",
765 | "id": "52"
766 | },
767 | {
768 | "name": "Concord Dawn",
769 | "rotation_period": "unknown",
770 | "orbital_period": "unknown",
771 | "diameter": "unknown",
772 | "climate": "unknown",
773 | "gravity": "unknown",
774 | "terrain": "jungles, forests, deserts",
775 | "surface_water": "unknown",
776 | "population": "unknown",
777 | "created": "2014-12-20 16:54:39.909000Z",
778 | "edited": "2014-12-20 20:58:18.512000Z",
779 | "url": "https://swapi.co/api/planets/53/",
780 | "id": "53"
781 | },
782 | {
783 | "name": "Zolan",
784 | "rotation_period": "unknown",
785 | "orbital_period": "unknown",
786 | "diameter": "unknown",
787 | "climate": "unknown",
788 | "gravity": "unknown",
789 | "terrain": "unknown",
790 | "surface_water": "unknown",
791 | "population": "unknown",
792 | "created": "2014-12-20 16:56:37.250000Z",
793 | "edited": "2014-12-20 20:58:18.514000Z",
794 | "url": "https://swapi.co/api/planets/54/",
795 | "id": "54"
796 | },
797 | {
798 | "name": "Ojom",
799 | "rotation_period": "unknown",
800 | "orbital_period": "unknown",
801 | "diameter": "unknown",
802 | "climate": "frigid",
803 | "gravity": "unknown",
804 | "terrain": "oceans, glaciers",
805 | "surface_water": "100",
806 | "population": "500000000",
807 | "created": "2014-12-20 17:27:41.286000Z",
808 | "edited": "2014-12-20 20:58:18.516000Z",
809 | "url": "https://swapi.co/api/planets/55/",
810 | "id": "55"
811 | },
812 | {
813 | "name": "Skako",
814 | "rotation_period": "27",
815 | "orbital_period": "384",
816 | "diameter": "unknown",
817 | "climate": "temperate",
818 | "gravity": "1",
819 | "terrain": "urban, vines",
820 | "surface_water": "unknown",
821 | "population": "500000000000",
822 | "created": "2014-12-20 17:50:47.864000Z",
823 | "edited": "2014-12-20 20:58:18.517000Z",
824 | "url": "https://swapi.co/api/planets/56/",
825 | "id": "56"
826 | },
827 | {
828 | "name": "Muunilinst",
829 | "rotation_period": "28",
830 | "orbital_period": "412",
831 | "diameter": "13800",
832 | "climate": "temperate",
833 | "gravity": "1",
834 | "terrain": "plains, forests, hills, mountains",
835 | "surface_water": "25",
836 | "population": "5000000000",
837 | "created": "2014-12-20 17:57:47.420000Z",
838 | "edited": "2014-12-20 20:58:18.519000Z",
839 | "url": "https://swapi.co/api/planets/57/",
840 | "id": "57"
841 | },
842 | {
843 | "name": "Shili",
844 | "rotation_period": "unknown",
845 | "orbital_period": "unknown",
846 | "diameter": "unknown",
847 | "climate": "temperate",
848 | "gravity": "1",
849 | "terrain": "cities, savannahs, seas, plains",
850 | "surface_water": "unknown",
851 | "population": "unknown",
852 | "created": "2014-12-20 18:43:14.049000Z",
853 | "edited": "2014-12-20 20:58:18.521000Z",
854 | "url": "https://swapi.co/api/planets/58/",
855 | "id": "58"
856 | },
857 | {
858 | "name": "Kalee",
859 | "rotation_period": "23",
860 | "orbital_period": "378",
861 | "diameter": "13850",
862 | "climate": "arid, temperate, tropical",
863 | "gravity": "1",
864 | "terrain": "rainforests, cliffs, canyons, seas",
865 | "surface_water": "unknown",
866 | "population": "4000000000",
867 | "created": "2014-12-20 19:43:51.278000Z",
868 | "edited": "2014-12-20 20:58:18.523000Z",
869 | "url": "https://swapi.co/api/planets/59/",
870 | "id": "59"
871 | },
872 | {
873 | "name": "Umbara",
874 | "rotation_period": "unknown",
875 | "orbital_period": "unknown",
876 | "diameter": "unknown",
877 | "climate": "unknown",
878 | "gravity": "unknown",
879 | "terrain": "unknown",
880 | "surface_water": "unknown",
881 | "population": "unknown",
882 | "created": "2014-12-20 20:18:36.256000Z",
883 | "edited": "2014-12-20 20:58:18.525000Z",
884 | "url": "https://swapi.co/api/planets/60/",
885 | "id": "60"
886 | },
887 | {
888 | "name": "Tatooine",
889 | "rotation_period": "23",
890 | "orbital_period": "304",
891 | "diameter": "10465",
892 | "climate": "arid",
893 | "gravity": "1 standard",
894 | "terrain": "desert",
895 | "surface_water": "1",
896 | "population": "200000",
897 | "created": "2014-12-09 13:50:49.641000Z",
898 | "edited": "2014-12-21 20:48:04.175778Z",
899 | "url": "https://swapi.co/api/planets/1/",
900 | "id": "1"
901 | },
902 | {
903 | "name": "Jakku",
904 | "rotation_period": "unknown",
905 | "orbital_period": "unknown",
906 | "diameter": "unknown",
907 | "climate": "unknown",
908 | "gravity": "unknown",
909 | "terrain": "deserts",
910 | "surface_water": "unknown",
911 | "population": "unknown",
912 | "created": "2015-04-17 06:55:57.556495Z",
913 | "edited": "2015-04-17 06:55:57.556551Z",
914 | "url": "https://swapi.co/api/planets/61/",
915 | "id": "61"
916 | }
917 | ]
918 |
--------------------------------------------------------------------------------
/example/database/data/people.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Luke Skywalker",
4 | "height": "172",
5 | "mass": "77",
6 | "hair_color": "blond",
7 | "skin_color": "fair",
8 | "eye_color": "blue",
9 | "birth_year": "19BBY",
10 | "gender": "male",
11 | "planet_id": "1",
12 | "created": "2014-12-09 13:50:51.644000Z",
13 | "edited": "2014-12-20 21:17:56.891000Z",
14 | "url": "https://swapi.co/api/people/1/",
15 | "id": "1"
16 | },
17 | {
18 | "name": "C-3PO",
19 | "height": "167",
20 | "mass": "75",
21 | "hair_color": "n/a",
22 | "skin_color": "gold",
23 | "eye_color": "yellow",
24 | "birth_year": "112BBY",
25 | "gender": "n/a",
26 | "planet_id": "1",
27 | "created": "2014-12-10 15:10:51.357000Z",
28 | "edited": "2014-12-20 21:17:50.309000Z",
29 | "url": "https://swapi.co/api/people/2/",
30 | "id": "2"
31 | },
32 | {
33 | "name": "R2-D2",
34 | "height": "96",
35 | "mass": "32",
36 | "hair_color": "n/a",
37 | "skin_color": "white, blue",
38 | "eye_color": "red",
39 | "birth_year": "33BBY",
40 | "gender": "n/a",
41 | "planet_id": "8",
42 | "created": "2014-12-10 15:11:50.376000Z",
43 | "edited": "2014-12-20 21:17:50.311000Z",
44 | "url": "https://swapi.co/api/people/3/",
45 | "id": "3"
46 | },
47 | {
48 | "name": "Darth Vader",
49 | "height": "202",
50 | "mass": "136",
51 | "hair_color": "none",
52 | "skin_color": "white",
53 | "eye_color": "yellow",
54 | "birth_year": "41.9BBY",
55 | "gender": "male",
56 | "planet_id": "1",
57 | "created": "2014-12-10 15:18:20.704000Z",
58 | "edited": "2014-12-20 21:17:50.313000Z",
59 | "url": "https://swapi.co/api/people/4/",
60 | "id": "4"
61 | },
62 | {
63 | "name": "Leia Organa",
64 | "height": "150",
65 | "mass": "49",
66 | "hair_color": "brown",
67 | "skin_color": "light",
68 | "eye_color": "brown",
69 | "birth_year": "19BBY",
70 | "gender": "female",
71 | "planet_id": "2",
72 | "created": "2014-12-10 15:20:09.791000Z",
73 | "edited": "2014-12-20 21:17:50.315000Z",
74 | "url": "https://swapi.co/api/people/5/",
75 | "id": "5"
76 | },
77 | {
78 | "name": "Owen Lars",
79 | "height": "178",
80 | "mass": "120",
81 | "hair_color": "brown, grey",
82 | "skin_color": "light",
83 | "eye_color": "blue",
84 | "birth_year": "52BBY",
85 | "gender": "male",
86 | "planet_id": "1",
87 | "created": "2014-12-10 15:52:14.024000Z",
88 | "edited": "2014-12-20 21:17:50.317000Z",
89 | "url": "https://swapi.co/api/people/6/",
90 | "id": "6"
91 | },
92 | {
93 | "name": "Beru Whitesun lars",
94 | "height": "165",
95 | "mass": "75",
96 | "hair_color": "brown",
97 | "skin_color": "light",
98 | "eye_color": "blue",
99 | "birth_year": "47BBY",
100 | "gender": "female",
101 | "planet_id": "1",
102 | "created": "2014-12-10 15:53:41.121000Z",
103 | "edited": "2014-12-20 21:17:50.319000Z",
104 | "url": "https://swapi.co/api/people/7/",
105 | "id": "7"
106 | },
107 | {
108 | "name": "R5-D4",
109 | "height": "97",
110 | "mass": "32",
111 | "hair_color": "n/a",
112 | "skin_color": "white, red",
113 | "eye_color": "red",
114 | "birth_year": "unknown",
115 | "gender": "n/a",
116 | "planet_id": "1",
117 | "created": "2014-12-10 15:57:50.959000Z",
118 | "edited": "2014-12-20 21:17:50.321000Z",
119 | "url": "https://swapi.co/api/people/8/",
120 | "id": "8"
121 | },
122 | {
123 | "name": "Biggs Darklighter",
124 | "height": "183",
125 | "mass": "84",
126 | "hair_color": "black",
127 | "skin_color": "light",
128 | "eye_color": "brown",
129 | "birth_year": "24BBY",
130 | "gender": "male",
131 | "planet_id": "1",
132 | "created": "2014-12-10 15:59:50.509000Z",
133 | "edited": "2014-12-20 21:17:50.323000Z",
134 | "url": "https://swapi.co/api/people/9/",
135 | "id": "9"
136 | },
137 | {
138 | "name": "Obi-Wan Kenobi",
139 | "height": "182",
140 | "mass": "77",
141 | "hair_color": "auburn, white",
142 | "skin_color": "fair",
143 | "eye_color": "blue-gray",
144 | "birth_year": "57BBY",
145 | "gender": "male",
146 | "planet_id": "20",
147 | "created": "2014-12-10 16:16:29.192000Z",
148 | "edited": "2014-12-20 21:17:50.325000Z",
149 | "url": "https://swapi.co/api/people/10/",
150 | "id": "10"
151 | },
152 | {
153 | "name": "Anakin Skywalker",
154 | "height": "188",
155 | "mass": "84",
156 | "hair_color": "blond",
157 | "skin_color": "fair",
158 | "eye_color": "blue",
159 | "birth_year": "41.9BBY",
160 | "gender": "male",
161 | "planet_id": "1",
162 | "created": "2014-12-10 16:20:44.310000Z",
163 | "edited": "2014-12-20 21:17:50.327000Z",
164 | "url": "https://swapi.co/api/people/11/",
165 | "id": "11"
166 | },
167 | {
168 | "name": "Wilhuff Tarkin",
169 | "height": "180",
170 | "mass": "unknown",
171 | "hair_color": "auburn, grey",
172 | "skin_color": "fair",
173 | "eye_color": "blue",
174 | "birth_year": "64BBY",
175 | "gender": "male",
176 | "planet_id": "21",
177 | "created": "2014-12-10 16:26:56.138000Z",
178 | "edited": "2014-12-20 21:17:50.330000Z",
179 | "url": "https://swapi.co/api/people/12/",
180 | "id": "12"
181 | },
182 | {
183 | "name": "Chewbacca",
184 | "height": "228",
185 | "mass": "112",
186 | "hair_color": "brown",
187 | "skin_color": "unknown",
188 | "eye_color": "blue",
189 | "birth_year": "200BBY",
190 | "gender": "male",
191 | "planet_id": "14",
192 | "created": "2014-12-10 16:42:45.066000Z",
193 | "edited": "2014-12-20 21:17:50.332000Z",
194 | "url": "https://swapi.co/api/people/13/",
195 | "id": "13"
196 | },
197 | {
198 | "name": "Han Solo",
199 | "height": "180",
200 | "mass": "80",
201 | "hair_color": "brown",
202 | "skin_color": "fair",
203 | "eye_color": "brown",
204 | "birth_year": "29BBY",
205 | "gender": "male",
206 | "planet_id": "22",
207 | "created": "2014-12-10 16:49:14.582000Z",
208 | "edited": "2014-12-20 21:17:50.334000Z",
209 | "url": "https://swapi.co/api/people/14/",
210 | "id": "14"
211 | },
212 | {
213 | "name": "Greedo",
214 | "height": "173",
215 | "mass": "74",
216 | "hair_color": "n/a",
217 | "skin_color": "green",
218 | "eye_color": "black",
219 | "birth_year": "44BBY",
220 | "gender": "male",
221 | "planet_id": "23",
222 | "created": "2014-12-10 17:03:30.334000Z",
223 | "edited": "2014-12-20 21:17:50.336000Z",
224 | "url": "https://swapi.co/api/people/15/",
225 | "id": "15"
226 | },
227 | {
228 | "name": "Jabba Desilijic Tiure",
229 | "height": "175",
230 | "mass": "1,358",
231 | "hair_color": "n/a",
232 | "skin_color": "green-tan, brown",
233 | "eye_color": "orange",
234 | "birth_year": "600BBY",
235 | "gender": "hermaphrodite",
236 | "planet_id": "24",
237 | "created": "2014-12-10 17:11:31.638000Z",
238 | "edited": "2014-12-20 21:17:50.338000Z",
239 | "url": "https://swapi.co/api/people/16/",
240 | "id": "16"
241 | },
242 | {
243 | "name": "Wedge Antilles",
244 | "height": "170",
245 | "mass": "77",
246 | "hair_color": "brown",
247 | "skin_color": "fair",
248 | "eye_color": "hazel",
249 | "birth_year": "21BBY",
250 | "gender": "male",
251 | "planet_id": "22",
252 | "created": "2014-12-12 11:08:06.469000Z",
253 | "edited": "2014-12-20 21:17:50.341000Z",
254 | "url": "https://swapi.co/api/people/18/",
255 | "id": "18"
256 | },
257 | {
258 | "name": "Jek Tono Porkins",
259 | "height": "180",
260 | "mass": "110",
261 | "hair_color": "brown",
262 | "skin_color": "fair",
263 | "eye_color": "blue",
264 | "birth_year": "unknown",
265 | "gender": "male",
266 | "planet_id": "26",
267 | "created": "2014-12-12 11:16:56.569000Z",
268 | "edited": "2014-12-20 21:17:50.343000Z",
269 | "url": "https://swapi.co/api/people/19/",
270 | "id": "19"
271 | },
272 | {
273 | "name": "Yoda",
274 | "height": "66",
275 | "mass": "17",
276 | "hair_color": "white",
277 | "skin_color": "green",
278 | "eye_color": "brown",
279 | "birth_year": "896BBY",
280 | "gender": "male",
281 | "planet_id": "28",
282 | "created": "2014-12-15 12:26:01.042000Z",
283 | "edited": "2014-12-20 21:17:50.345000Z",
284 | "url": "https://swapi.co/api/people/20/",
285 | "id": "20"
286 | },
287 | {
288 | "name": "Palpatine",
289 | "height": "170",
290 | "mass": "75",
291 | "hair_color": "grey",
292 | "skin_color": "pale",
293 | "eye_color": "yellow",
294 | "birth_year": "82BBY",
295 | "gender": "male",
296 | "planet_id": "8",
297 | "created": "2014-12-15 12:48:05.971000Z",
298 | "edited": "2014-12-20 21:17:50.347000Z",
299 | "url": "https://swapi.co/api/people/21/",
300 | "id": "21"
301 | },
302 | {
303 | "name": "Boba Fett",
304 | "height": "183",
305 | "mass": "78.2",
306 | "hair_color": "black",
307 | "skin_color": "fair",
308 | "eye_color": "brown",
309 | "birth_year": "31.5BBY",
310 | "gender": "male",
311 | "planet_id": "10",
312 | "created": "2014-12-15 12:49:32.457000Z",
313 | "edited": "2014-12-20 21:17:50.349000Z",
314 | "url": "https://swapi.co/api/people/22/",
315 | "id": "22"
316 | },
317 | {
318 | "name": "IG-88",
319 | "height": "200",
320 | "mass": "140",
321 | "hair_color": "none",
322 | "skin_color": "metal",
323 | "eye_color": "red",
324 | "birth_year": "15BBY",
325 | "gender": "none",
326 | "planet_id": "28",
327 | "created": "2014-12-15 12:51:10.076000Z",
328 | "edited": "2014-12-20 21:17:50.351000Z",
329 | "url": "https://swapi.co/api/people/23/",
330 | "id": "23"
331 | },
332 | {
333 | "name": "Bossk",
334 | "height": "190",
335 | "mass": "113",
336 | "hair_color": "none",
337 | "skin_color": "green",
338 | "eye_color": "red",
339 | "birth_year": "53BBY",
340 | "gender": "male",
341 | "planet_id": "29",
342 | "created": "2014-12-15 12:53:49.297000Z",
343 | "edited": "2014-12-20 21:17:50.355000Z",
344 | "url": "https://swapi.co/api/people/24/",
345 | "id": "24"
346 | },
347 | {
348 | "name": "Lando Calrissian",
349 | "height": "177",
350 | "mass": "79",
351 | "hair_color": "black",
352 | "skin_color": "dark",
353 | "eye_color": "brown",
354 | "birth_year": "31BBY",
355 | "gender": "male",
356 | "planet_id": "30",
357 | "created": "2014-12-15 12:56:32.683000Z",
358 | "edited": "2014-12-20 21:17:50.357000Z",
359 | "url": "https://swapi.co/api/people/25/",
360 | "id": "25"
361 | },
362 | {
363 | "name": "Lobot",
364 | "height": "175",
365 | "mass": "79",
366 | "hair_color": "none",
367 | "skin_color": "light",
368 | "eye_color": "blue",
369 | "birth_year": "37BBY",
370 | "gender": "male",
371 | "planet_id": "6",
372 | "created": "2014-12-15 13:01:57.178000Z",
373 | "edited": "2014-12-20 21:17:50.359000Z",
374 | "url": "https://swapi.co/api/people/26/",
375 | "id": "26"
376 | },
377 | {
378 | "name": "Ackbar",
379 | "height": "180",
380 | "mass": "83",
381 | "hair_color": "none",
382 | "skin_color": "brown mottle",
383 | "eye_color": "orange",
384 | "birth_year": "41BBY",
385 | "gender": "male",
386 | "planet_id": "31",
387 | "created": "2014-12-18 11:07:50.584000Z",
388 | "edited": "2014-12-20 21:17:50.362000Z",
389 | "url": "https://swapi.co/api/people/27/",
390 | "id": "27"
391 | },
392 | {
393 | "name": "Mon Mothma",
394 | "height": "150",
395 | "mass": "unknown",
396 | "hair_color": "auburn",
397 | "skin_color": "fair",
398 | "eye_color": "blue",
399 | "birth_year": "48BBY",
400 | "gender": "female",
401 | "planet_id": "32",
402 | "created": "2014-12-18 11:12:38.895000Z",
403 | "edited": "2014-12-20 21:17:50.364000Z",
404 | "url": "https://swapi.co/api/people/28/",
405 | "id": "28"
406 | },
407 | {
408 | "name": "Arvel Crynyd",
409 | "height": "unknown",
410 | "mass": "unknown",
411 | "hair_color": "brown",
412 | "skin_color": "fair",
413 | "eye_color": "brown",
414 | "birth_year": "unknown",
415 | "gender": "male",
416 | "planet_id": "28",
417 | "created": "2014-12-18 11:16:33.020000Z",
418 | "edited": "2014-12-20 21:17:50.367000Z",
419 | "url": "https://swapi.co/api/people/29/",
420 | "id": "29"
421 | },
422 | {
423 | "name": "Wicket Systri Warrick",
424 | "height": "88",
425 | "mass": "20",
426 | "hair_color": "brown",
427 | "skin_color": "brown",
428 | "eye_color": "brown",
429 | "birth_year": "8BBY",
430 | "gender": "male",
431 | "planet_id": "7",
432 | "created": "2014-12-18 11:21:58.954000Z",
433 | "edited": "2014-12-20 21:17:50.369000Z",
434 | "url": "https://swapi.co/api/people/30/",
435 | "id": "30"
436 | },
437 | {
438 | "name": "Nien Nunb",
439 | "height": "160",
440 | "mass": "68",
441 | "hair_color": "none",
442 | "skin_color": "grey",
443 | "eye_color": "black",
444 | "birth_year": "unknown",
445 | "gender": "male",
446 | "planet_id": "33",
447 | "created": "2014-12-18 11:26:18.541000Z",
448 | "edited": "2014-12-20 21:17:50.371000Z",
449 | "url": "https://swapi.co/api/people/31/",
450 | "id": "31"
451 | },
452 | {
453 | "name": "Qui-Gon Jinn",
454 | "height": "193",
455 | "mass": "89",
456 | "hair_color": "brown",
457 | "skin_color": "fair",
458 | "eye_color": "blue",
459 | "birth_year": "92BBY",
460 | "gender": "male",
461 | "planet_id": "28",
462 | "created": "2014-12-19 16:54:53.618000Z",
463 | "edited": "2014-12-20 21:17:50.375000Z",
464 | "url": "https://swapi.co/api/people/32/",
465 | "id": "32"
466 | },
467 | {
468 | "name": "Nute Gunray",
469 | "height": "191",
470 | "mass": "90",
471 | "hair_color": "none",
472 | "skin_color": "mottled green",
473 | "eye_color": "red",
474 | "birth_year": "unknown",
475 | "gender": "male",
476 | "planet_id": "18",
477 | "created": "2014-12-19 17:05:57.357000Z",
478 | "edited": "2014-12-20 21:17:50.377000Z",
479 | "url": "https://swapi.co/api/people/33/",
480 | "id": "33"
481 | },
482 | {
483 | "name": "Finis Valorum",
484 | "height": "170",
485 | "mass": "unknown",
486 | "hair_color": "blond",
487 | "skin_color": "fair",
488 | "eye_color": "blue",
489 | "birth_year": "91BBY",
490 | "gender": "male",
491 | "planet_id": "9",
492 | "created": "2014-12-19 17:21:45.915000Z",
493 | "edited": "2014-12-20 21:17:50.379000Z",
494 | "url": "https://swapi.co/api/people/34/",
495 | "id": "34"
496 | },
497 | {
498 | "name": "Jar Jar Binks",
499 | "height": "196",
500 | "mass": "66",
501 | "hair_color": "none",
502 | "skin_color": "orange",
503 | "eye_color": "orange",
504 | "birth_year": "52BBY",
505 | "gender": "male",
506 | "planet_id": "8",
507 | "created": "2014-12-19 17:29:32.489000Z",
508 | "edited": "2014-12-20 21:17:50.383000Z",
509 | "url": "https://swapi.co/api/people/36/",
510 | "id": "36"
511 | },
512 | {
513 | "name": "Roos Tarpals",
514 | "height": "224",
515 | "mass": "82",
516 | "hair_color": "none",
517 | "skin_color": "grey",
518 | "eye_color": "orange",
519 | "birth_year": "unknown",
520 | "gender": "male",
521 | "planet_id": "8",
522 | "created": "2014-12-19 17:32:56.741000Z",
523 | "edited": "2014-12-20 21:17:50.385000Z",
524 | "url": "https://swapi.co/api/people/37/",
525 | "id": "37"
526 | },
527 | {
528 | "name": "Rugor Nass",
529 | "height": "206",
530 | "mass": "unknown",
531 | "hair_color": "none",
532 | "skin_color": "green",
533 | "eye_color": "orange",
534 | "birth_year": "unknown",
535 | "gender": "male",
536 | "planet_id": "8",
537 | "created": "2014-12-19 17:33:38.909000Z",
538 | "edited": "2014-12-20 21:17:50.388000Z",
539 | "url": "https://swapi.co/api/people/38/",
540 | "id": "38"
541 | },
542 | {
543 | "name": "Ric Olié",
544 | "height": "183",
545 | "mass": "unknown",
546 | "hair_color": "brown",
547 | "skin_color": "fair",
548 | "eye_color": "blue",
549 | "birth_year": "unknown",
550 | "gender": "male",
551 | "planet_id": "8",
552 | "created": "2014-12-19 17:45:01.522000Z",
553 | "edited": "2014-12-20 21:17:50.392000Z",
554 | "url": "https://swapi.co/api/people/39/",
555 | "id": "39"
556 | },
557 | {
558 | "name": "Watto",
559 | "height": "137",
560 | "mass": "unknown",
561 | "hair_color": "black",
562 | "skin_color": "blue, grey",
563 | "eye_color": "yellow",
564 | "birth_year": "unknown",
565 | "gender": "male",
566 | "planet_id": "34",
567 | "created": "2014-12-19 17:48:54.647000Z",
568 | "edited": "2014-12-20 21:17:50.395000Z",
569 | "url": "https://swapi.co/api/people/40/",
570 | "id": "40"
571 | },
572 | {
573 | "name": "Sebulba",
574 | "height": "112",
575 | "mass": "40",
576 | "hair_color": "none",
577 | "skin_color": "grey, red",
578 | "eye_color": "orange",
579 | "birth_year": "unknown",
580 | "gender": "male",
581 | "planet_id": "35",
582 | "created": "2014-12-19 17:53:02.586000Z",
583 | "edited": "2014-12-20 21:17:50.397000Z",
584 | "url": "https://swapi.co/api/people/41/",
585 | "id": "41"
586 | },
587 | {
588 | "name": "Quarsh Panaka",
589 | "height": "183",
590 | "mass": "unknown",
591 | "hair_color": "black",
592 | "skin_color": "dark",
593 | "eye_color": "brown",
594 | "birth_year": "62BBY",
595 | "gender": "male",
596 | "planet_id": "8",
597 | "created": "2014-12-19 17:55:43.348000Z",
598 | "edited": "2014-12-20 21:17:50.399000Z",
599 | "url": "https://swapi.co/api/people/42/",
600 | "id": "42"
601 | },
602 | {
603 | "name": "Shmi Skywalker",
604 | "height": "163",
605 | "mass": "unknown",
606 | "hair_color": "black",
607 | "skin_color": "fair",
608 | "eye_color": "brown",
609 | "birth_year": "72BBY",
610 | "gender": "female",
611 | "planet_id": "1",
612 | "created": "2014-12-19 17:57:41.191000Z",
613 | "edited": "2014-12-20 21:17:50.401000Z",
614 | "url": "https://swapi.co/api/people/43/",
615 | "id": "43"
616 | },
617 | {
618 | "name": "Darth Maul",
619 | "height": "175",
620 | "mass": "80",
621 | "hair_color": "none",
622 | "skin_color": "red",
623 | "eye_color": "yellow",
624 | "birth_year": "54BBY",
625 | "gender": "male",
626 | "planet_id": "36",
627 | "created": "2014-12-19 18:00:41.929000Z",
628 | "edited": "2014-12-20 21:17:50.403000Z",
629 | "url": "https://swapi.co/api/people/44/",
630 | "id": "44"
631 | },
632 | {
633 | "name": "Bib Fortuna",
634 | "height": "180",
635 | "mass": "unknown",
636 | "hair_color": "none",
637 | "skin_color": "pale",
638 | "eye_color": "pink",
639 | "birth_year": "unknown",
640 | "gender": "male",
641 | "planet_id": "37",
642 | "created": "2014-12-20 09:47:02.512000Z",
643 | "edited": "2014-12-20 21:17:50.407000Z",
644 | "url": "https://swapi.co/api/people/45/",
645 | "id": "45"
646 | },
647 | {
648 | "name": "Ayla Secura",
649 | "height": "178",
650 | "mass": "55",
651 | "hair_color": "none",
652 | "skin_color": "blue",
653 | "eye_color": "hazel",
654 | "birth_year": "48BBY",
655 | "gender": "female",
656 | "planet_id": "37",
657 | "created": "2014-12-20 09:48:01.172000Z",
658 | "edited": "2014-12-20 21:17:50.409000Z",
659 | "url": "https://swapi.co/api/people/46/",
660 | "id": "46"
661 | },
662 | {
663 | "name": "Dud Bolt",
664 | "height": "94",
665 | "mass": "45",
666 | "hair_color": "none",
667 | "skin_color": "blue, grey",
668 | "eye_color": "yellow",
669 | "birth_year": "unknown",
670 | "gender": "male",
671 | "planet_id": "39",
672 | "created": "2014-12-20 09:57:31.858000Z",
673 | "edited": "2014-12-20 21:17:50.414000Z",
674 | "url": "https://swapi.co/api/people/48/",
675 | "id": "48"
676 | },
677 | {
678 | "name": "Gasgano",
679 | "height": "122",
680 | "mass": "unknown",
681 | "hair_color": "none",
682 | "skin_color": "white, blue",
683 | "eye_color": "black",
684 | "birth_year": "unknown",
685 | "gender": "male",
686 | "planet_id": "40",
687 | "created": "2014-12-20 10:02:12.223000Z",
688 | "edited": "2014-12-20 21:17:50.416000Z",
689 | "url": "https://swapi.co/api/people/49/",
690 | "id": "49"
691 | },
692 | {
693 | "name": "Ben Quadinaros",
694 | "height": "163",
695 | "mass": "65",
696 | "hair_color": "none",
697 | "skin_color": "grey, green, yellow",
698 | "eye_color": "orange",
699 | "birth_year": "unknown",
700 | "gender": "male",
701 | "planet_id": "41",
702 | "created": "2014-12-20 10:08:33.777000Z",
703 | "edited": "2014-12-20 21:17:50.417000Z",
704 | "url": "https://swapi.co/api/people/50/",
705 | "id": "50"
706 | },
707 | {
708 | "name": "Mace Windu",
709 | "height": "188",
710 | "mass": "84",
711 | "hair_color": "none",
712 | "skin_color": "dark",
713 | "eye_color": "brown",
714 | "birth_year": "72BBY",
715 | "gender": "male",
716 | "planet_id": "42",
717 | "created": "2014-12-20 10:12:30.846000Z",
718 | "edited": "2014-12-20 21:17:50.420000Z",
719 | "url": "https://swapi.co/api/people/51/",
720 | "id": "51"
721 | },
722 | {
723 | "name": "Ki-Adi-Mundi",
724 | "height": "198",
725 | "mass": "82",
726 | "hair_color": "white",
727 | "skin_color": "pale",
728 | "eye_color": "yellow",
729 | "birth_year": "92BBY",
730 | "gender": "male",
731 | "planet_id": "43",
732 | "created": "2014-12-20 10:15:32.293000Z",
733 | "edited": "2014-12-20 21:17:50.422000Z",
734 | "url": "https://swapi.co/api/people/52/",
735 | "id": "52"
736 | },
737 | {
738 | "name": "Kit Fisto",
739 | "height": "196",
740 | "mass": "87",
741 | "hair_color": "none",
742 | "skin_color": "green",
743 | "eye_color": "black",
744 | "birth_year": "unknown",
745 | "gender": "male",
746 | "planet_id": "44",
747 | "created": "2014-12-20 10:18:57.202000Z",
748 | "edited": "2014-12-20 21:17:50.424000Z",
749 | "url": "https://swapi.co/api/people/53/",
750 | "id": "53"
751 | },
752 | {
753 | "name": "Eeth Koth",
754 | "height": "171",
755 | "mass": "unknown",
756 | "hair_color": "black",
757 | "skin_color": "brown",
758 | "eye_color": "brown",
759 | "birth_year": "unknown",
760 | "gender": "male",
761 | "planet_id": "45",
762 | "created": "2014-12-20 10:26:47.902000Z",
763 | "edited": "2014-12-20 21:17:50.427000Z",
764 | "url": "https://swapi.co/api/people/54/",
765 | "id": "54"
766 | },
767 | {
768 | "name": "Adi Gallia",
769 | "height": "184",
770 | "mass": "50",
771 | "hair_color": "none",
772 | "skin_color": "dark",
773 | "eye_color": "blue",
774 | "birth_year": "unknown",
775 | "gender": "female",
776 | "planet_id": "9",
777 | "created": "2014-12-20 10:29:11.661000Z",
778 | "edited": "2014-12-20 21:17:50.432000Z",
779 | "url": "https://swapi.co/api/people/55/",
780 | "id": "55"
781 | },
782 | {
783 | "name": "Saesee Tiin",
784 | "height": "188",
785 | "mass": "unknown",
786 | "hair_color": "none",
787 | "skin_color": "pale",
788 | "eye_color": "orange",
789 | "birth_year": "unknown",
790 | "gender": "male",
791 | "planet_id": "47",
792 | "created": "2014-12-20 10:32:11.669000Z",
793 | "edited": "2014-12-20 21:17:50.434000Z",
794 | "url": "https://swapi.co/api/people/56/",
795 | "id": "56"
796 | },
797 | {
798 | "name": "Yarael Poof",
799 | "height": "264",
800 | "mass": "unknown",
801 | "hair_color": "none",
802 | "skin_color": "white",
803 | "eye_color": "yellow",
804 | "birth_year": "unknown",
805 | "gender": "male",
806 | "planet_id": "48",
807 | "created": "2014-12-20 10:34:48.725000Z",
808 | "edited": "2014-12-20 21:17:50.437000Z",
809 | "url": "https://swapi.co/api/people/57/",
810 | "id": "57"
811 | },
812 | {
813 | "name": "Plo Koon",
814 | "height": "188",
815 | "mass": "80",
816 | "hair_color": "none",
817 | "skin_color": "orange",
818 | "eye_color": "black",
819 | "birth_year": "22BBY",
820 | "gender": "male",
821 | "planet_id": "49",
822 | "created": "2014-12-20 10:49:19.859000Z",
823 | "edited": "2014-12-20 21:17:50.439000Z",
824 | "url": "https://swapi.co/api/people/58/",
825 | "id": "58"
826 | },
827 | {
828 | "name": "Mas Amedda",
829 | "height": "196",
830 | "mass": "unknown",
831 | "hair_color": "none",
832 | "skin_color": "blue",
833 | "eye_color": "blue",
834 | "birth_year": "unknown",
835 | "gender": "male",
836 | "planet_id": "50",
837 | "created": "2014-12-20 10:53:26.457000Z",
838 | "edited": "2014-12-20 21:17:50.442000Z",
839 | "url": "https://swapi.co/api/people/59/",
840 | "id": "59"
841 | },
842 | {
843 | "name": "Gregar Typho",
844 | "height": "185",
845 | "mass": "85",
846 | "hair_color": "black",
847 | "skin_color": "dark",
848 | "eye_color": "brown",
849 | "birth_year": "unknown",
850 | "gender": "male",
851 | "planet_id": "8",
852 | "created": "2014-12-20 11:10:10.381000Z",
853 | "edited": "2014-12-20 21:17:50.445000Z",
854 | "url": "https://swapi.co/api/people/60/",
855 | "id": "60"
856 | },
857 | {
858 | "name": "Cordé",
859 | "height": "157",
860 | "mass": "unknown",
861 | "hair_color": "brown",
862 | "skin_color": "light",
863 | "eye_color": "brown",
864 | "birth_year": "unknown",
865 | "gender": "female",
866 | "planet_id": "8",
867 | "created": "2014-12-20 11:11:39.630000Z",
868 | "edited": "2014-12-20 21:17:50.449000Z",
869 | "url": "https://swapi.co/api/people/61/",
870 | "id": "61"
871 | },
872 | {
873 | "name": "Cliegg Lars",
874 | "height": "183",
875 | "mass": "unknown",
876 | "hair_color": "brown",
877 | "skin_color": "fair",
878 | "eye_color": "blue",
879 | "birth_year": "82BBY",
880 | "gender": "male",
881 | "planet_id": "1",
882 | "created": "2014-12-20 15:59:03.958000Z",
883 | "edited": "2014-12-20 21:17:50.451000Z",
884 | "url": "https://swapi.co/api/people/62/",
885 | "id": "62"
886 | },
887 | {
888 | "name": "Poggle the Lesser",
889 | "height": "183",
890 | "mass": "80",
891 | "hair_color": "none",
892 | "skin_color": "green",
893 | "eye_color": "yellow",
894 | "birth_year": "unknown",
895 | "gender": "male",
896 | "planet_id": "11",
897 | "created": "2014-12-20 16:40:43.977000Z",
898 | "edited": "2014-12-20 21:17:50.453000Z",
899 | "url": "https://swapi.co/api/people/63/",
900 | "id": "63"
901 | },
902 | {
903 | "name": "Luminara Unduli",
904 | "height": "170",
905 | "mass": "56.2",
906 | "hair_color": "black",
907 | "skin_color": "yellow",
908 | "eye_color": "blue",
909 | "birth_year": "58BBY",
910 | "gender": "female",
911 | "planet_id": "51",
912 | "created": "2014-12-20 16:45:53.668000Z",
913 | "edited": "2014-12-20 21:17:50.455000Z",
914 | "url": "https://swapi.co/api/people/64/",
915 | "id": "64"
916 | },
917 | {
918 | "name": "Barriss Offee",
919 | "height": "166",
920 | "mass": "50",
921 | "hair_color": "black",
922 | "skin_color": "yellow",
923 | "eye_color": "blue",
924 | "birth_year": "40BBY",
925 | "gender": "female",
926 | "planet_id": "51",
927 | "created": "2014-12-20 16:46:40.440000Z",
928 | "edited": "2014-12-20 21:17:50.457000Z",
929 | "url": "https://swapi.co/api/people/65/",
930 | "id": "65"
931 | },
932 | {
933 | "name": "Dormé",
934 | "height": "165",
935 | "mass": "unknown",
936 | "hair_color": "brown",
937 | "skin_color": "light",
938 | "eye_color": "brown",
939 | "birth_year": "unknown",
940 | "gender": "female",
941 | "planet_id": "8",
942 | "created": "2014-12-20 16:49:14.640000Z",
943 | "edited": "2014-12-20 21:17:50.460000Z",
944 | "url": "https://swapi.co/api/people/66/",
945 | "id": "66"
946 | },
947 | {
948 | "name": "Dooku",
949 | "height": "193",
950 | "mass": "80",
951 | "hair_color": "white",
952 | "skin_color": "fair",
953 | "eye_color": "brown",
954 | "birth_year": "102BBY",
955 | "gender": "male",
956 | "planet_id": "52",
957 | "created": "2014-12-20 16:52:14.726000Z",
958 | "edited": "2014-12-20 21:17:50.462000Z",
959 | "url": "https://swapi.co/api/people/67/",
960 | "id": "67"
961 | },
962 | {
963 | "name": "Bail Prestor Organa",
964 | "height": "191",
965 | "mass": "unknown",
966 | "hair_color": "black",
967 | "skin_color": "tan",
968 | "eye_color": "brown",
969 | "birth_year": "67BBY",
970 | "gender": "male",
971 | "planet_id": "2",
972 | "created": "2014-12-20 16:53:08.575000Z",
973 | "edited": "2014-12-20 21:17:50.463000Z",
974 | "url": "https://swapi.co/api/people/68/",
975 | "id": "68"
976 | },
977 | {
978 | "name": "Jango Fett",
979 | "height": "183",
980 | "mass": "79",
981 | "hair_color": "black",
982 | "skin_color": "tan",
983 | "eye_color": "brown",
984 | "birth_year": "66BBY",
985 | "gender": "male",
986 | "planet_id": "53",
987 | "created": "2014-12-20 16:54:41.620000Z",
988 | "edited": "2014-12-20 21:17:50.465000Z",
989 | "url": "https://swapi.co/api/people/69/",
990 | "id": "69"
991 | },
992 | {
993 | "name": "Zam Wesell",
994 | "height": "168",
995 | "mass": "55",
996 | "hair_color": "blonde",
997 | "skin_color": "fair, green, yellow",
998 | "eye_color": "yellow",
999 | "birth_year": "unknown",
1000 | "gender": "female",
1001 | "planet_id": "54",
1002 | "created": "2014-12-20 16:57:44.471000Z",
1003 | "edited": "2014-12-20 21:17:50.468000Z",
1004 | "url": "https://swapi.co/api/people/70/",
1005 | "id": "70"
1006 | },
1007 | {
1008 | "name": "Dexter Jettster",
1009 | "height": "198",
1010 | "mass": "102",
1011 | "hair_color": "none",
1012 | "skin_color": "brown",
1013 | "eye_color": "yellow",
1014 | "birth_year": "unknown",
1015 | "gender": "male",
1016 | "planet_id": "55",
1017 | "created": "2014-12-20 17:28:27.248000Z",
1018 | "edited": "2014-12-20 21:17:50.470000Z",
1019 | "url": "https://swapi.co/api/people/71/",
1020 | "id": "71"
1021 | },
1022 | {
1023 | "name": "Lama Su",
1024 | "height": "229",
1025 | "mass": "88",
1026 | "hair_color": "none",
1027 | "skin_color": "grey",
1028 | "eye_color": "black",
1029 | "birth_year": "unknown",
1030 | "gender": "male",
1031 | "planet_id": "10",
1032 | "created": "2014-12-20 17:30:50.416000Z",
1033 | "edited": "2014-12-20 21:17:50.473000Z",
1034 | "url": "https://swapi.co/api/people/72/",
1035 | "id": "72"
1036 | },
1037 | {
1038 | "name": "Taun We",
1039 | "height": "213",
1040 | "mass": "unknown",
1041 | "hair_color": "none",
1042 | "skin_color": "grey",
1043 | "eye_color": "black",
1044 | "birth_year": "unknown",
1045 | "gender": "female",
1046 | "planet_id": "10",
1047 | "created": "2014-12-20 17:31:21.195000Z",
1048 | "edited": "2014-12-20 21:17:50.474000Z",
1049 | "url": "https://swapi.co/api/people/73/",
1050 | "id": "73"
1051 | },
1052 | {
1053 | "name": "Jocasta Nu",
1054 | "height": "167",
1055 | "mass": "unknown",
1056 | "hair_color": "white",
1057 | "skin_color": "fair",
1058 | "eye_color": "blue",
1059 | "birth_year": "unknown",
1060 | "gender": "female",
1061 | "planet_id": "9",
1062 | "created": "2014-12-20 17:32:51.996000Z",
1063 | "edited": "2014-12-20 21:17:50.476000Z",
1064 | "url": "https://swapi.co/api/people/74/",
1065 | "id": "74"
1066 | },
1067 | {
1068 | "name": "Ratts Tyerell",
1069 | "height": "79",
1070 | "mass": "15",
1071 | "hair_color": "none",
1072 | "skin_color": "grey, blue",
1073 | "eye_color": "unknown",
1074 | "birth_year": "unknown",
1075 | "gender": "male",
1076 | "planet_id": "38",
1077 | "created": "2014-12-20 09:53:15.086000Z",
1078 | "edited": "2016-06-30 12:52:19.604868Z",
1079 | "url": "https://swapi.co/api/people/47/",
1080 | "id": "47"
1081 | },
1082 | {
1083 | "name": "R4-P17",
1084 | "height": "96",
1085 | "mass": "unknown",
1086 | "hair_color": "none",
1087 | "skin_color": "silver, red",
1088 | "eye_color": "red, blue",
1089 | "birth_year": "unknown",
1090 | "gender": "female",
1091 | "planet_id": "28",
1092 | "created": "2014-12-20 17:43:36.409000Z",
1093 | "edited": "2014-12-20 21:17:50.478000Z",
1094 | "url": "https://swapi.co/api/people/75/",
1095 | "id": "75"
1096 | },
1097 | {
1098 | "name": "Wat Tambor",
1099 | "height": "193",
1100 | "mass": "48",
1101 | "hair_color": "none",
1102 | "skin_color": "green, grey",
1103 | "eye_color": "unknown",
1104 | "birth_year": "unknown",
1105 | "gender": "male",
1106 | "planet_id": "56",
1107 | "created": "2014-12-20 17:53:52.607000Z",
1108 | "edited": "2014-12-20 21:17:50.481000Z",
1109 | "url": "https://swapi.co/api/people/76/",
1110 | "id": "76"
1111 | },
1112 | {
1113 | "name": "San Hill",
1114 | "height": "191",
1115 | "mass": "unknown",
1116 | "hair_color": "none",
1117 | "skin_color": "grey",
1118 | "eye_color": "gold",
1119 | "birth_year": "unknown",
1120 | "gender": "male",
1121 | "planet_id": "57",
1122 | "created": "2014-12-20 17:58:17.049000Z",
1123 | "edited": "2014-12-20 21:17:50.484000Z",
1124 | "url": "https://swapi.co/api/people/77/",
1125 | "id": "77"
1126 | },
1127 | {
1128 | "name": "Shaak Ti",
1129 | "height": "178",
1130 | "mass": "57",
1131 | "hair_color": "none",
1132 | "skin_color": "red, blue, white",
1133 | "eye_color": "black",
1134 | "birth_year": "unknown",
1135 | "gender": "female",
1136 | "planet_id": "58",
1137 | "created": "2014-12-20 18:44:01.103000Z",
1138 | "edited": "2014-12-20 21:17:50.486000Z",
1139 | "url": "https://swapi.co/api/people/78/",
1140 | "id": "78"
1141 | },
1142 | {
1143 | "name": "Grievous",
1144 | "height": "216",
1145 | "mass": "159",
1146 | "hair_color": "none",
1147 | "skin_color": "brown, white",
1148 | "eye_color": "green, yellow",
1149 | "birth_year": "unknown",
1150 | "gender": "male",
1151 | "planet_id": "59",
1152 | "created": "2014-12-20 19:43:53.348000Z",
1153 | "edited": "2014-12-20 21:17:50.488000Z",
1154 | "url": "https://swapi.co/api/people/79/",
1155 | "id": "79"
1156 | },
1157 | {
1158 | "name": "Tarfful",
1159 | "height": "234",
1160 | "mass": "136",
1161 | "hair_color": "brown",
1162 | "skin_color": "brown",
1163 | "eye_color": "blue",
1164 | "birth_year": "unknown",
1165 | "gender": "male",
1166 | "planet_id": "14",
1167 | "created": "2014-12-20 19:46:34.209000Z",
1168 | "edited": "2014-12-20 21:17:50.491000Z",
1169 | "url": "https://swapi.co/api/people/80/",
1170 | "id": "80"
1171 | },
1172 | {
1173 | "name": "Raymus Antilles",
1174 | "height": "188",
1175 | "mass": "79",
1176 | "hair_color": "brown",
1177 | "skin_color": "light",
1178 | "eye_color": "brown",
1179 | "birth_year": "unknown",
1180 | "gender": "male",
1181 | "planet_id": "2",
1182 | "created": "2014-12-20 19:49:35.583000Z",
1183 | "edited": "2014-12-20 21:17:50.493000Z",
1184 | "url": "https://swapi.co/api/people/81/",
1185 | "id": "81"
1186 | },
1187 | {
1188 | "name": "Sly Moore",
1189 | "height": "178",
1190 | "mass": "48",
1191 | "hair_color": "none",
1192 | "skin_color": "pale",
1193 | "eye_color": "white",
1194 | "birth_year": "unknown",
1195 | "gender": "female",
1196 | "planet_id": "60",
1197 | "created": "2014-12-20 20:18:37.619000Z",
1198 | "edited": "2014-12-20 21:17:50.496000Z",
1199 | "url": "https://swapi.co/api/people/82/",
1200 | "id": "82"
1201 | },
1202 | {
1203 | "name": "Tion Medon",
1204 | "height": "206",
1205 | "mass": "80",
1206 | "hair_color": "none",
1207 | "skin_color": "grey",
1208 | "eye_color": "black",
1209 | "birth_year": "unknown",
1210 | "gender": "male",
1211 | "planet_id": "12",
1212 | "created": "2014-12-20 20:35:04.260000Z",
1213 | "edited": "2014-12-20 21:17:50.498000Z",
1214 | "url": "https://swapi.co/api/people/83/",
1215 | "id": "83"
1216 | },
1217 | {
1218 | "name": "Finn",
1219 | "height": "unknown",
1220 | "mass": "unknown",
1221 | "hair_color": "black",
1222 | "skin_color": "dark",
1223 | "eye_color": "dark",
1224 | "birth_year": "unknown",
1225 | "gender": "male",
1226 | "planet_id": "28",
1227 | "created": "2015-04-17 06:52:40.793621Z",
1228 | "edited": "2015-04-17 06:52:40.793674Z",
1229 | "url": "https://swapi.co/api/people/84/",
1230 | "id": "84"
1231 | },
1232 | {
1233 | "name": "Rey",
1234 | "height": "unknown",
1235 | "mass": "unknown",
1236 | "hair_color": "brown",
1237 | "skin_color": "light",
1238 | "eye_color": "hazel",
1239 | "birth_year": "unknown",
1240 | "gender": "female",
1241 | "planet_id": "28",
1242 | "created": "2015-04-17 06:54:01.495077Z",
1243 | "edited": "2015-04-17 06:54:01.495128Z",
1244 | "url": "https://swapi.co/api/people/85/",
1245 | "id": "85"
1246 | },
1247 | {
1248 | "name": "Poe Dameron",
1249 | "height": "unknown",
1250 | "mass": "unknown",
1251 | "hair_color": "brown",
1252 | "skin_color": "light",
1253 | "eye_color": "brown",
1254 | "birth_year": "unknown",
1255 | "gender": "male",
1256 | "planet_id": "28",
1257 | "created": "2015-04-17 06:55:21.622786Z",
1258 | "edited": "2015-04-17 06:55:21.622835Z",
1259 | "url": "https://swapi.co/api/people/86/",
1260 | "id": "86"
1261 | },
1262 | {
1263 | "name": "BB8",
1264 | "height": "unknown",
1265 | "mass": "unknown",
1266 | "hair_color": "none",
1267 | "skin_color": "none",
1268 | "eye_color": "black",
1269 | "birth_year": "unknown",
1270 | "gender": "none",
1271 | "planet_id": "28",
1272 | "created": "2015-04-17 06:57:38.061346Z",
1273 | "edited": "2015-04-17 06:57:38.061453Z",
1274 | "url": "https://swapi.co/api/people/87/",
1275 | "id": "87"
1276 | },
1277 | {
1278 | "name": "Captain Phasma",
1279 | "height": "unknown",
1280 | "mass": "unknown",
1281 | "hair_color": "unknown",
1282 | "skin_color": "unknown",
1283 | "eye_color": "unknown",
1284 | "birth_year": "unknown",
1285 | "gender": "female",
1286 | "planet_id": "28",
1287 | "created": "2015-10-13 10:35:39.229823Z",
1288 | "edited": "2015-10-13 10:35:39.229894Z",
1289 | "url": "https://swapi.co/api/people/88/",
1290 | "id": "88"
1291 | },
1292 | {
1293 | "name": "Padmé Amidala",
1294 | "height": "165",
1295 | "mass": "45",
1296 | "hair_color": "brown",
1297 | "skin_color": "light",
1298 | "eye_color": "brown",
1299 | "birth_year": "46BBY",
1300 | "gender": "female",
1301 | "planet_id": "8",
1302 | "created": "2014-12-19 17:28:26.926000Z",
1303 | "edited": "2016-04-20 17:06:31.502555Z",
1304 | "url": "https://swapi.co/api/people/35/",
1305 | "id": "35"
1306 | }
1307 | ]
1308 |
--------------------------------------------------------------------------------