Contributing¶
This guide covers development setup, code style, testing, and contributing to the Etsy Integration project.
Development Setup¶
Prerequisites¶
- Python: 3.10 or higher
- Frappe Framework: Latest compatible version
- ERPNext: Version 15
- Git: For version control
- MariaDB/PostgreSQL: Database
- Redis: For caching and background jobs
Setting Up Development Environment¶
1. Clone the Repository¶
Or for direct development:
2. Install the App¶
3. Install Development Dependencies¶
4. Set Up Pre-commit Hooks¶
The project uses pre-commit hooks for code quality:
Run hooks manually:
Development Commands¶
Navigate to your bench directory for all commands:
Install/Uninstall App¶
# Install
bench --site development.localhost install-app etsy
# Uninstall (without backup)
bench --site development.localhost uninstall-app etsy --no-backup
Run Tests¶
# Run all tests for the app
bench run-tests --app etsy
# Run a specific test module
bench run-tests --app etsy --module etsy.etsy.doctype.etsy_shop.test_etsy_shop
# Run a specific test class
bench run-tests --app etsy --test TestEtsyShop
# With verbose output
bench run-tests --app etsy --verbose
# With coverage report
bench run-tests --app etsy --coverage
Build Assets¶
Run Linters¶
# Run all pre-commit hooks
pre-commit run --all-files
# Run Ruff linter only
ruff check etsy/
# Auto-fix issues
ruff check --fix etsy/
Database Migrations¶
After changing doctype schemas:
Console Access¶
For debugging and testing:
# Example: Test API connection
import frappe
shop = frappe.get_doc("Etsy Shop", "Your Shop Name")
from etsy.api import EtsyAPI
api = EtsyAPI(shop)
print(api.getMe())
Code Style¶
The project follows strict code style guidelines enforced by linters.
Python¶
Linter: Ruff
Rules:
- Indentation: Tabs
- Quotes: Double quotes
- Line Length: 110 characters
- Target: Python 3.10
- Lint Rules: F (pyflakes), E (pycodestyle errors), W (pycodestyle warnings), I (isort), UP (pyupgrade), B (flake8-bugbear), RUF (ruff-specific)
Example:
def example_function(param1: str, param2: int) -> dict:
"""
Docstring with proper formatting.
Args:
param1: Description
param2: Description
Returns:
Description of return value
"""
result = {
"key": "value",
"number": param2,
}
return result
JavaScript¶
Linter: ESLint + Prettier
Rules:
- Indentation: Tabs (size 4)
- Quotes: Double quotes preferred
- Target: ES2022
- Naming: Snake_case acceptable (Frappe convention)
Example:
frappe.ui.form.on("Etsy Shop", {
refresh: function(frm) {
if (frm.doc.status === "Connected") {
frm.add_custom_button(__("Import Listings"), function() {
frm.call("import_listings");
});
}
}
});
EditorConfig¶
The project includes .editorconfig for consistent editor settings:
- Charset: UTF-8
- Line Endings: LF
- Trim Trailing Whitespace: Yes
- Insert Final Newline: Yes
- Indentation: Tabs (except JSON: 2 spaces)
Project Structure¶
etsy/
├── api.py # HTTP client and API wrapper
├── datastruct.py # Pydantic models for Etsy API responses
├── hooks.py # Frappe hooks and custom fields
├── install.py # Installation and uninstallation routines
└── etsy/
└── doctype/
├── etsy_shop/ # Main doctype for shop management
├── etsy_settings/ # Global sync settings
└── etsy_listing/ # Per-listing configuration
Testing¶
Test Structure¶
Tests are located in doctype directories:
etsy/etsy/doctype/etsy_shop/
├── etsy_shop.py
├── etsy_shop.json
├── etsy_shop.js
└── test_etsy_shop.py # Test file
Writing Tests¶
Example Test:
import frappe
import unittest
class TestEtsyShop(unittest.TestCase):
def setUp(self):
"""Set up test fixtures"""
self.shop = frappe.get_doc({
"doctype": "Etsy Shop",
"shop_name": "Test Shop",
"company": "_Test Company",
# ... other required fields
})
self.shop.insert()
def tearDown(self):
"""Clean up after tests"""
frappe.delete_doc("Etsy Shop", "Test Shop", force=True)
def test_shop_creation(self):
"""Test shop document creation"""
self.assertEqual(self.shop.shop_name, "Test Shop")
self.assertEqual(self.shop.status, "Disconnected")
def test_auth_header_generation(self):
"""Test OAuth header generation"""
self.shop.access_token = "test_token"
header = self.shop.get_auth_header()
self.assertEqual(header["Authorization"], "Bearer test_token")
Mocking External APIs¶
For testing API interactions without hitting Etsy:
from unittest.mock import patch, MagicMock
class TestEtsyAPI(unittest.TestCase):
@patch('etsy.api.SyncCacheClient')
def test_get_me(self, mock_client):
"""Test getMe API call"""
# Mock the response
mock_response = MagicMock()
mock_response.json.return_value = {
"user_id": 12345,
"login_name": "test_user",
"primary_email": "test@example.com"
}
mock_client.return_value.get.return_value = mock_response
# Test the API call
from etsy.api import EtsyAPI
api = EtsyAPI(self.shop)
result = api.getMe()
self.assertEqual(result.user_id, 12345)
Contributing Workflow¶
- Fork the Repository
-
Fork on GitHub: maeurerdev/erpnext-etsy
-
Create a Feature Branch
-
Make Changes
- Write code following style guidelines
- Add tests for new features
-
Update documentation if needed
-
Run Tests and Linters
-
Commit Changes
-
Push to Your Fork
-
Create Pull Request
- Open PR on GitHub
- Provide clear description
- Reference any related issues
Commit Message Convention¶
Follow conventional commits:
Format: type(scope): description
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, no logic change)refactor: Code refactoringtest: Adding or updating testschore: Maintenance tasks
Examples:
feat(api): Add support for listing variants
fix(sync): Handle missing item gracefully
docs(readme): Update installation instructions
test(shop): Add OAuth flow tests
Pull Request Guidelines¶
Good PR:
- Clear, descriptive title
- Detailed description of changes
- References related issues (#123)
- Includes tests for new features
- All tests passing
- Follows code style guidelines
- Updates documentation if needed
PR Template:
## Description
Brief description of what this PR does.
## Changes
- Change 1
- Change 2
## Testing
How to test these changes.
## Related Issues
Fixes #123
Related to #456
## Checklist
- [ ] Tests pass
- [ ] Code follows style guidelines
- [ ] Documentation updated
- [ ] No breaking changes (or documented)
Debugging¶
Enable Debug Mode¶
In site_config.json:
Restart bench:
Logging¶
Use Frappe's logging:
import frappe
# Info logging
frappe.log("Processing receipt: {0}".format(receipt_id))
# Error logging
frappe.log_error(
title="Etsy Import Error",
message=frappe.get_traceback()
)
# Debug logging (when developer_mode is on)
frappe.logger().debug("Debug message")
View logs:
Using pdb¶
Insert breakpoint:
Or with Python 3.7+:
Browser DevTools¶
For frontend debugging:
- Open browser DevTools (F12)
- Check Console for JavaScript errors
- Use Network tab to inspect API calls
- Use Sources tab to debug JavaScript
Test API Connection¶
from etsy.api import EtsyAPI
shop = frappe.get_doc("Etsy Shop", "Your Shop Name")
api = EtsyAPI(shop)
print(api.get("/application/users/me")) # Should return user data
exit()
Check Database State¶
-- Count Etsy-related records
SELECT COUNT(*) FROM `tabCustomer` WHERE etsy_customer_id IS NOT NULL;
SELECT COUNT(*) FROM `tabSales Order` WHERE etsy_order_id IS NOT NULL;
SELECT COUNT(*) FROM `tabEtsy Listing`;
exit;
Review Scheduled Jobs¶
import frappe
jobs = frappe.get_all("Scheduled Job Log",
filters={"scheduled_job_type": ["like", "%etsy%"]},
fields=["name", "status", "creation"],
order_by="creation desc",
limit=10)
for job in jobs:
print(f"{job.creation}: {job.status}")
exit()
Getting Help¶
Issues and Bugs¶
Report issues on GitHub: https://github.com/maeurerdev/erpnext-etsy/issues
Include:
- ERPNext version
- App version (commit hash)
- Detailed error messages
- Steps to reproduce
- Expected vs. actual behavior