Official Python client for Nodify Headless CMS
Project description
Nodify Python Client
Official Python client for Nodify Headless CMS
A powerful asynchronous Python client built with aiohttp to interact with your Nodify instance, enabling you to manage content, nodes, translations, and more programmatically.
📋 Prerequisites
Before using this client, you need access to a Nodify Headless CMS instance. Nodify is a powerful, multilingual headless CMS that delivers content through APIs to any channel (websites, mobile apps, IoT, etc.).
Option 1: Use the Public Demo (For Testing)
You can test the client against the public Nodify demo instance:
- Demo URL:
https://nodify-core.azirar.ovh - Credentials:
- Username:
admin - Password:
Admin13579++
- Username:
⚠️ The demo server is a shared environment and may be reset at any time. It is accessible daily, typically from 10:00 AM to 12:00 AM (UTC+1).
Option 2: Run Nodify Locally with Docker (Recommended for Development)
You can run your own instance of Nodify using Docker Compose. You have two choices depending on whether you have existing MongoDB and Redis instances.
A. Using External MongoDB & Redis
If you already have MongoDB and Redis running, use this docker-compose.yml:
services:
nodify-core:
image: azirar/nodify-core:latest
environment:
MONGO_URL: "mongodb://your-mongo-host:27017/nodify" # Replace with your Mongo URL
ADMIN_PWD: "YourAdminPassword" # Replace with your admin password
API_URL: "http://nodify-api:1080" # Internal URL for the API service
TZ: "${TZ:-Europe/Paris}"
REDIS_URL: "redis://your-redis-host:6379" # Replace with your Redis URL
JAVA_OPTS: "-Xmx768m -Xms384m"
ports:
- "7804:8080"
nodify-api:
image: azirar/nodify-api:latest
environment:
MONGO_URL: "mongodb://your-mongo-host:27017/nodify" # Replace with your Mongo URL
TZ: "${TZ:-Europe/Paris}"
REDIS_URL: "redis://your-redis-host:6379" # Replace with your Redis URL
JAVA_OPTS: "-Xmx512m -Xms256m"
ports:
- "7805:1080"
nodify-ui:
image: azirar/nodify-ui:latest
depends_on:
- nodify-core
- nodify-api
ports:
- "7821:80"
environment:
CORE_URL: "http://nodify-core:8080"
API_URL: "http://nodify-api:1080"
B. Using Dockerized MongoDB & Redis (All-in-One)
If you don't have existing databases, this setup will create everything together:
services:
mongo:
image: mongo:latest
volumes:
- mongo-data:/data/db
- mongo-config:/data/configdb
ports:
- "27017:27017"
redis:
image: redis:latest
ports:
- "6379:6379"
nodify-core:
image: azirar/nodify-core:latest
depends_on:
- mongo
- redis
environment:
MONGO_URL: "mongodb://mongo:27017/nodify"
ADMIN_PWD: "Admin123"
API_URL: "http://nodify-api:1080"
TZ: "${TZ:-Europe/Paris}"
REDIS_URL: "redis://redis:6379"
JAVA_OPTS: "-Xmx768m -Xms384m"
ports:
- "7804:8080"
nodify-api:
image: azirar/nodify-api:latest
depends_on:
- mongo
- redis
environment:
MONGO_URL: "mongodb://mongo:27017/nodify"
TZ: "${TZ:-Europe/Paris}"
REDIS_URL: "redis://redis:6379"
JAVA_OPTS: "-Xmx512m -Xms256m"
ports:
- "7805:1080"
nodify-ui:
image: azirar/nodify-ui:latest
depends_on:
- nodify-core
- nodify-api
ports:
- "7821:80"
environment:
CORE_URL: "http://nodify-core:8080"
API_URL: "http://nodify-api:1080"
volumes:
mongo-data:
mongo-config:
To start the entire stack:
docker-compose up -d
After running either Docker setup, you can access the Nodify UI at http://localhost:7821. The backend services will be available at the mapped ports.
📦 Installation
From PyPI
pip install nodify-python-client
From source (development)
git clone https://github.com/AZIRARM/nodify-clients.git
cd nodify-clients/python
pip install -e .
🚀 Quick Start
Here's a simple example to get you started:
import asyncio
import uuid
from nodify_client import ReactiveNodifyClient, Node, StatusEnum
async def quick_start():
# 1. Create a client instance pointing to your Nodify Core instance
client = ReactiveNodifyClient.create(
ReactiveNodifyClient.builder()
.with_base_url("http://localhost:7804") # URL of your nodify-core instance
.build()
)
try:
# 2. Authenticate
auth = await client.login("admin", "Admin123")
print("✅ Login successful")
# 3. Create a new node (a structural element like a 'Page' or 'Site')
node = Node(
name="My First Page",
code=f"MY_FIRST_PAGE_{str(uuid.uuid4())[:8].upper()}",
slug="my-first-page",
type="PAGE",
status=StatusEnum.SNAPSHOT,
default_language="en",
environment_code="production"
)
saved_node = await client.save_node(node)
print(f"✅ Node created with code: {saved_node.code}")
# 4. Find all nodes
all_nodes = await client.find_all_nodes()
print(f"📊 Total nodes: {len(all_nodes)}")
except Exception as e:
print(f"❌ An error occurred: {e}")
finally:
# 5. Close the client's HTTP connections
await client.close()
if __name__ == "__main__":
asyncio.run(quick_start())
✨ Core Concepts
Nodes vs Content Nodes
- Node: Represents a structural element in your content hierarchy (e.g., a
SITE, aPAGE, aFOLDER). Nodes can have child nodes and content nodes attached. - Content Node: Represents the actual content itself. It can be of various types (
HTML,JSON,DATA,FILE, etc.). It's where your translations and dynamic values live.
Dynamic Directives
Nodify supports powerful directives within your content strings:
$translate(KEY): Automatically replaces the directive with the translated value for the current language based on translations attached to the content node.$value(KEY): Replaces the directive with a dynamic value stored in the content node'svaluesarray.
📝 Advanced Example: Creating Multilingual Content
This example demonstrates creating a parent node, a child page, and an HTML content node with translations and a dynamic value.
import asyncio
import uuid
from nodify_client import (
ReactiveNodifyClient,
Node,
ContentNode,
Translation,
Value,
ContentTypeEnum,
StatusEnum
)
async def create_complete_scenario():
# 1. Create a client instance
client = ReactiveNodifyClient.create(
ReactiveNodifyClient.builder()
.with_base_url("http://localhost:7804")
.build()
)
try:
# 2. Login
await client.login("admin", "Admin123")
print("✅ Login successful")
# 3. Create a parent node (e.g., your website)
parent_node = Node(
name="My Multilingual Site",
code=f"MY_SITE_{str(uuid.uuid4())[:8].upper()}",
slug="my-site",
type="SITE",
status=StatusEnum.SNAPSHOT,
default_language="en",
environment_code="production"
)
saved_parent = await client.save_node(parent_node)
print(f"✅ Parent node created: {saved_parent.name}")
# 4. Create a child page node under the website
child_node = Node(
parent_code=saved_parent.code,
name="Welcome Page",
code=f"WELCOME_PAGE_{str(uuid.uuid4())[:8].upper()}",
slug="welcome",
type="PAGE",
status=StatusEnum.SNAPSHOT,
default_language="en",
environment_code="production"
)
saved_child = await client.save_node(child_node)
print(f"✅ Child node created: {saved_child.name}")
# 5. Create HTML content with translations and values
translations = [
Translation(key="GREETING", language="en", value="Hello"),
Translation(key="GREETING", language="fr", value="Bonjour"),
Translation(key="GREETING", language="es", value="Hola"),
]
values = [
Value(key="USER_NAME", value="Python Developer")
]
content_node = ContentNode(
parent_code=saved_child.code,
type=ContentTypeEnum.HTML,
title="Dynamic Welcome",
code=f"WELCOME_HTML_{str(uuid.uuid4())[:8].upper()}",
slug="welcome-html",
language="en",
status=StatusEnum.SNAPSHOT,
environment_code="production",
content="<h1>$translate(GREETING), $value(USER_NAME)!</h1>\n<p>Welcome to your Nodify site built with Python.</p>",
translations=translations,
values=values
)
saved_content = await client.save_content_node(content_node)
print(f"✅ Content node created: {saved_content.title}")
# 6. Publish the content
await client.publish_content_node(saved_content.code, True)
print("✅ Content published")
# 7. Publish the parent node
await client.publish_node(saved_parent.code)
print("✅ Parent node published")
# 8. Display final information
print("\n" + "="*60)
print("🎯 FINAL SCENARIO SUMMARY")
print("="*60)
print(f"\n📁 PARENT NODE: {saved_parent.name} ({saved_parent.code})")
print(f"📄 CHILD NODE: {saved_child.name} ({saved_child.code})")
print(f"📝 CONTENT NODE: {saved_content.title} ({saved_content.code})")
print("\n🌍 TRANSLATIONS:")
for t in saved_content.translations:
print(f" - {t.key} [{t.language}]: {t.value}")
print("\n🔢 VALUES:")
for v in saved_content.values:
print(f" - {v.key}: {v.value}")
except Exception as e:
print(f"❌ An error occurred: {e}")
finally:
await client.close()
if __name__ == "__main__":
asyncio.run(create_complete_scenario())
📖 API Reference
The client provides methods for all Nodify API endpoints, organized by resource:
Authentication
async login(email: str, password: str) -> AuthResponseasync logout() -> None
Nodes
async find_all_nodes() -> List[Node]async save_node(node: Node) -> Nodeasync find_node_by_code(code: str) -> Nodeasync find_node_by_code_and_status(code: str, status: str) -> Nodeasync find_nodes_by_parent_code(code: str) -> List[Node]async publish_node(code: str) -> Nodeasync delete_node(code: str) -> bool
Content Nodes
async find_all_content_nodes() -> List[ContentNode]async save_content_node(content_node: ContentNode) -> ContentNodeasync find_content_node_by_code_and_status(code: str, status: str) -> ContentNodeasync find_content_nodes_by_node_code(code: str) -> List[ContentNode]async publish_content_node(code: str, publish: bool) -> ContentNodeasync delete_content_node(code: str) -> bool
Users
async find_all_users() -> List[UserPost]async save_user(user: UserPost) -> UserPostasync find_user_by_email(email: str) -> UserPostasync change_password(password_data: UserPassword) -> boolasync delete_user(user_id: str) -> bool
Plugins
async find_not_deleted_plugins() -> List[Plugin]async save_plugin(plugin: Plugin) -> Pluginasync enable_plugin(plugin_id: str) -> Pluginasync disable_plugin(plugin_id: str) -> Pluginasync export_plugin(plugin_id: str) -> bytes
Feedback
async find_all_feedback() -> List[Feedback]async save_feedback(feedback: Feedback) -> Feedbackasync get_content_charts() -> List[FeedbackCharts]
Locks
async acquire_lock(code: str) -> boolasync release_lock(code: str) -> boolasync get_lock_owner(code: str) -> LockInfoasync get_all_locks() -> List[LockInfo]
Data
async save_data(data: Data) -> Dataasync find_data_by_key(key: str) -> Dataasync find_data_by_content_code(code: str, params: PaginationParams = None) -> List[Data]
⚙️ Configuration
Client Builder Options
client = ReactiveNodifyClient.create(
ReactiveNodifyClient.builder()
.with_base_url("https://your-nodify-instance.com")
.with_timeout(30000) # Timeout in milliseconds
.with_auth_token("your-jwt-token") # Optional: pre-authenticated token
.with_header("X-Custom-Header", "value") # Custom headers
.build()
)
Pagination
from nodify_client import PaginationParams
params = PaginationParams(current_page=1, limit=20) # Page 1, 20 items per page
data = await client.find_data_by_content_code("CONTENT_CODE", params)
📋 Requirements
- Python 3.8 or higher
- aiohttp >= 3.8.0
🤝 Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
📄 License
This client library is licensed under the MIT License. See the LICENSE file for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nodify_python_client-1.0.0.tar.gz.
File metadata
- Download URL: nodify_python_client-1.0.0.tar.gz
- Upload date:
- Size: 27.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d83ca6f4b67104f58858102a0522f864e39c86034ff759237836298ed549ed5
|
|
| MD5 |
8b93f1031ef324b1a3ccd0b6e06fc983
|
|
| BLAKE2b-256 |
2099079e9fe9f25d372cdb673a3990372a06ac12ec977cbe0c06b0c90373858f
|
File details
Details for the file nodify_python_client-1.0.0-py3-none-any.whl.
File metadata
- Download URL: nodify_python_client-1.0.0-py3-none-any.whl
- Upload date:
- Size: 23.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe8e83a27607d1d3448d2ea339e332741e0dbb1144a8b68cbe6d13a15c0b3c11
|
|
| MD5 |
043d7221e3e193af284cb00b15641f9a
|
|
| BLAKE2b-256 |
db2be323716e1bfffb7d79767bd434289fda6e2aeb74a72e2d99bb17a10be480
|