python-fmdata
A Python wrapper for the FileMaker Data API with Django-style ORM functionality.
Overview
python-fmdata
is a comprehensive Python library that provides both low-level access to the FileMaker Data API and a high-level ORM (Object-Relational Mapping) interface similar to Django's ORM. It supports multiple authentication methods and makes it easy to work with FileMaker databases from Python applications.
Features
- Django-style ORM: Query FileMaker databases using familiar ORM patterns
- Multiple Authentication Methods: Username/password, OAuth, and Claris Cloud support
- Portal Relationships: Full support for FileMaker portal (related) records
- Efficient Querying: Chunked results, prefetching, and pagination support
- CRUD Operations: Create, read, update, and delete records with ease
- Low-level API Access: Direct access to FileMaker Data API when needed
- Type Safety: Built with type hints and marshmallow for data validation
Installation
pip install fmdata
For Claris Cloud support:
pip install fmdata[cloud]
Requirements
- Python 3.8+
- FileMaker Server with Data API enabled
- Valid FileMaker database with appropriate privileges
Quick Start
1. Setup Connection and Authentication
import fmdata
from fmdata.session_providers import UsernamePasswordSessionProvider
session_provider = UsernamePasswordSessionProvider(
username="your_username",
password="your_password"
)
fm_client = fmdata.FMClient(
url="https://your-filemaker-server.com",
database="your_database",
login_provider=session_provider
)
2. Define Models
from marshmallow import fields
from fmdata.orm import Model, PortalField, PortalModel
class ClassPortal(PortalModel):
name = fields.Str(required=False, data_key="class_portal_name::Name")
description = fields.Str(required=False, data_key="class_portal_name::Description")
class Student(Model):
class Meta:
client = fm_client
layout = 'student_layout'
pk = fields.Str(data_key="PrimaryKey")
full_name = fields.Str(data_key="FullName")
enrollment_date = fields.Date(data_key="EnrollmentDate")
graduation_year = fields.Integer(data_key="GraduationYear", allow_none=True)
classes = PortalField(model=ClassPortal, name="class_portal_name")
3. Query Records
students = Student.objects.order_by("pk").find(full_name__raw="*")
student_john = Student.objects.find(full_name="John Doe")
students_of_2024_but_not_john = Student.objects.find(graduation_year=2024).omit(full_name="John Doe")
result_set = (Student.objects
.order_by("pk")
.find(full_name__raw="*")
.chunk_size(1000)
.prefetch_portal("classes", limit=100)
)[:1000]
for student in result_set:
print(f"Student: {student.pk} - {student.full_name}")
for class_record in student.classes.only_prefetched():
print(f" Class: {class_record.name} - {class_record.description}")
4. Create, Update, and Delete Records
student = Student.objects.create(
full_name="John Doe",
enrollment_date=date(2024, 1, 15),
graduation_year=2028
)
student.full_name = "John Smith"
student.save()
student.classes.create(name="Mathematics", description="Advanced Math Course")
student.delete()
Student.objects.find(graduation_year=2024).delete()
Authentication Methods
Username/Password Authentication
from fmdata.session_providers import UsernamePasswordSessionProvider
session_provider = UsernamePasswordSessionProvider(
username="your_username",
password="your_password"
)
OAuth Authentication
from fmdata.session_providers import OAuthSessionProvider
session_provider = OAuthSessionProvider(
oauth_request_id="your_oauth_request_id",
oauth_identifier="your_oauth_identifier"
)
Claris Cloud Authentication
from fmdata.session_providers import ClarisCloudSessionProvider
session_provider = ClarisCloudSessionProvider(
claris_id_name="your_claris_id",
claris_id_password="your_password"
)
Advanced Querying
Field Criteria
from fmdata.orm import Criteria
students = Student.objects.find(
full_name__contains="John",
graduation_year__gte=2024,
enrollment_date__range=(date(2020, 1, 1), date(2024, 12, 31))
)
students = Student.objects.find(full_name__raw="John*")
students = Student.objects.find(graduation_year__not_empty=True)
Ordering and Pagination
students = Student.objects.order_by("graduation_year", "-full_name").find()
first_10 = Student.objects.find()[0:10]
next_10 = Student.objects.find()[10:20]
for student in Student.objects.find().chunk_size(1000):
process_student(student)
Portal Operations
students = (Student.objects
.prefetch_portal("classes", limit=50)
.find())
for student in students:
classes = student.classes.all()[:30]
classes = student.classes.only_prefetched()
classes = student.classes.avoid_prefetch_cache()
student.classes.create(name="New Class", description="Description")
for class_record in classes:
class_record.description += " (Updated)"
class_record.save()
student.classes.filter(name="Old Class").delete()
Error Handling
from fmdata import FMErrorEnum
from fmdata.results import FileMakerErrorException
try:
student = Student.objects.get(pk="nonexistent")
except FileMakerErrorException as e:
if e.error_code in (FMErrorEnum.INSUFFICIENT_PRIVILEGES, FMErrorEnum.FIELD_ACCESS_DENIED):
print("Insufficient privileges, please check your user permissions.")
else:
print(f"FileMaker error: {e.error_code} - {e.message}")
Low-Level API Access
For direct FileMaker Data API access:
result = fm_client.create_record(
layout="Students",
field_data={"FullName": "Jane Doe", "EnrollmentDate": "01/15/2024"}
).raise_exception_if_has_error()
record = fm_client.get_record(layout="Students", record_id="123").raise_exception_if_has_error()
results = fm_client.find(
layout="Students",
query=[{"FullName": "John*"}],
sort=[{"fieldName": "FullName", "sortOrder": "ascend"}]
).raise_exception_if_has_error()
script_result = fm_client.perform_script(
layout="Students",
name="MyScript",
param="parameter_value"
).raise_exception_if_has_error()
Configuration Options
fm_client = fmdata.FMClient(
url="https://your-server.com",
database="your_database",
login_provider=session_provider,
api_version="v1",
connection_timeout=10,
read_timeout=30,
verify_ssl=True,
auto_manage_session=True
)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License.
Author
Lorenzo De Siena (dev.lorenzo.desiena@gmail.com)
Acknowledgements
We would like to thank:
Links