A Beginner’s Guide to Building a SOAP-Based CRUD API with Python and Spyne
Remember when we talked about SOAP in “Understanding SOAP: The Old Guard of Web Services”? Today, let’s put theory into practice by building a basic CRUD API using SOAP. We’ll be using Python, our programming language friend.
Let’s Get Started
This project focuses on the essentials of SOAP for API development. To keep things organized and readable, we’ll structure our project using the MVC (Model-View-Controller) pattern. For simplicity, our data will be stored in a JSON file.
In Python, we have a fantastic library called Spyne, It simplifies communication between applications using SOAP protocols. We’ll leverage its capabilities to build our API efficiently.
Why Spyne?
Spyne not only streamlines SOAP integration but also ensures robust communication channels between client and server. Its intuitive design and Pythonic syntax make it a standout choice for projects like ours.
Now, let’s dive into the code and see how Spyne helps us create seamless communication in our SOAP-based CRUD API.
Setting Up the Project
Let’s start by outlining the project structure and the key components involved:
Project Structure
- app.py: This file initializes the SOAP server using Spyne framework.
- controllers.py: Contains functions to handle CRUD operations and data persistence.
- models.py: Defines the data models used in the application.
- views.py: Implements the SOAP service methods using Spyne decorators.
Requirements
Ensure you have the following Python packages installed, listed in requirements.txt
spyne
zeep
lxml
Implementation Details
User Model and Controller First, let’s define our User and Response model in models.py:
from spyne import Unicode
from spyne.model.complex import ComplexModel, Array
# User model
class User(ComplexModel):
id = Unicode
name = Unicode
email = Unicode
description = Unicode
# Response Message
class ResponseData(ComplexModel):
user = User
users = Array(User)
success = Unicode
message = Unicode
Next, in controllers.py, we implement CRUD functions using JSON file as a storage:
import json
import uuid
from models import User
DATA_FILE = 'users.json'
def load_users():
try:
with open(DATA_FILE, 'r') as file:
return json.load(file)
except FileNotFoundError:
return {}
def save_users(users):
with open(DATA_FILE, 'w') as file:
json.dump(users, file, indent=4)
def add_user(name, email, description):
users = load_users()
user_id = str(uuid.uuid4())
users[user_id] = {
"id": user_id,
"name": name,
"email": email,
"description": description
}
save_users(users)
return user_id
def get_user(user_id):
users = load_users()
return users.get(user_id)
def update_user(user_id, name, email, description):
users = load_users()
if user_id in users:
users[user_id] = {
"id": user_id,
"name": name,
"email": email,
"description": description
}
save_users(users)
return True
return False
def delete_user(user_id):
users = load_users()
if user_id in users:
del users[user_id]
save_users(users)
return True
return False
def list_users():
return list(load_users().values())
SOAP Service Implementation
Now, let’s define our SOAP service methods in views.py using Spyne decorators:
from spyne import ServiceBase, rpc, Unicode, Array
from models import User, ResponseData
import controllers
class UserService(ServiceBase):
@rpc(Unicode, Unicode, Unicode, _returns=Unicode)
def add_user(ctx, name, email, description):
return controllers.add_user(name, email, description)
@rpc(Unicode, _returns=ResponseData)
def get_user(ctx, user_id):
user = controllers.get_user(user_id)
if user:
user = User(
id=user['id'],
name=user['name'],
email=user['email'],
description=user['description']
)
return ResponseData(user=user, success='1', message='User found')
return ResponseData(user=User(), success='0', message='User not found.')
@rpc(_returns=ResponseData)
def get_users(ctx):
users = controllers.list_users()
if users:
users_data = [User(
id=user['id'],
name=user['name'],
email=user['email'],
description=user['description']
) for user in users]
return ResponseData(users=users_data, success='1', message="Users found.")
return ResponseData(users=None, success='0', message="No users found.")
@rpc(Unicode, Unicode, Unicode, Unicode, _returns=ResponseData)
def update_user(ctx, user_id, name, email, description):
success = controllers.update_user(user_id, name, email, description)
message = "User updated successfully" if success else "User not found"
return ResponseData(user=User(), success='1' if success else '0', message=message)
@rpc(Unicode, _returns=ResponseData)
def delete_user(ctx, user_id):
success = controllers.delete_user(user_id)
message = "User deleted successfully" if success else "User not found"
return ResponseData(user=User(), success='1' if success else '0', message=message)
Running the SOAP Server
Finally, in app.py, we setup and run the SOAP server using Spyne and WSGI:
from spyne import Application
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
from views import UserService
from wsgiref.simple_server import make_server
# Application setup
application = Application(
[UserService],
tns='api.users',
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11()
)
# WSGI Application
wsgi_application = WsgiApplication(application)
# Server setup
if __name__ == '__main__':
server = make_server('127.0.0.1', 8000, wsgi_application) # Change to localhost or 127.0.0.1
print("SOAP server running on http://127.0.0.1:8000")
server.serve_forever()
Testing the API with Postman
To interact with our SOAP API, we can use tools like Postman. Here are examples of requests you can make:
Add User (POST)
- Endpoint: http://localhost:8000
- Body:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:api="api.users">
<soapenv:Header/>
<soapenv:Body>
<api:add_user>
<name>John Doe</name>
<email>john.doe@example.com</email>
<description>Example user</description>
</api:add_user>
</soapenv:Body>
</soapenv:Envelope>
Response:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:api="api.users">
<soapenv:Body>
<api:add_userResponse>
<return>GeneratedUserUUID</return>
</api:add_userResponse>
</soapenv:Body>
</soapenv:Envelope>
Get User (GET)
- Endpoint: http://localhost:8000
- Body:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:api="api.users">
<soapenv:Header/>
<soapenv:Body>
<api:get_user>
<user_id>GeneratedUserUUID</user_id>
</api:get_user>
</soapenv:Body>
</soapenv:Envelope>
Response:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:api="api.users">
<soapenv:Body>
<api:get_userResponse>
<return>
<user>
<id>GeneratedUserUUID</id>
<name>John Doe</name>
<email>john.doe@example.com</email>
<description>Example user</description>
</user>
<success>1</success>
<message>User found</message>
</return>
</api:get_userResponse>
</soapenv:Body>
</soapenv:Envelope>
Conclusion
In this tutorial, we’ve explored how to build a Simple CRUD API using SOAP architecture in Python with Spyne. This approach provides a structured and formal way to communicate between applications, making it suitable for enterprise scenarios.
Bye!
Thank you for reading. Feel free to suggest code improvement on GitHub and comment about this simple implementation. Thanks 🫂
GitHub Link:
https://github.com/mariomthree/crud-soap-api