Skip to main content

Python library for simplifying the interaction with Myer's PIM API (Akeneo's Product management API)

Project description

Myer PIM SDK

PyPI version License: MIT

A comprehensive Python SDK for integrating with Akeneo REST API, specifically designed for Myer's Product Information Management (PIM) system.

Features

  • Full Akeneo REST API Coverage: Support for products, product models, families, attributes, categories, and media files
  • Rate Limiting: Built-in throttling to respect Myer's 20 calls per minute limit
  • Synchronous & Asynchronous: Both sync and async client implementations
  • Type Safety: Full Pydantic model support with type hints
  • Bulk Operations: Efficient bulk updates for multiple entities
  • Media File Handling: Streamlined image and file upload for product enrichment
  • Error Handling: Comprehensive error handling with specific exception types
  • Retry Logic: Automatic retry on transient failures
  • OAuth2 Authentication: Automatic token management and refresh

Installation

pip install myer-pim-sdk

For development with Redis support:

pip install myer-pim-sdk[redis,dev]

Quick Start

Basic Setup

from myer_pim_sdk import AkeneoClient

# Initialize the client
client = AkeneoClient(
    client_id="your_client_id",
    client_secret="your_client_secret", 
    base_url="https://your-pim.akeneo.com"
)

# Get a product by identifier (SKU)
product = client.products.get_by_identifier("SKU123")
print(f"Product: {product.identifier} - {product.values}")

Async Usage

from myer_pim_sdk import AkeneoAsyncClient
import asyncio

async def main():
    client = AkeneoAsyncClient(
        client_id="your_client_id",
        client_secret="your_client_secret",
        base_url="https://your-pim.akeneo.com"
    )
    
    try:
        # Get product asynchronously
        product = await client.products.get_by_identifier_async("SKU123")
        print(f"Product: {product.identifier}")
        
        # List products with pagination
        products = await client.products.list_by_uuid_async(limit=50, paginated=True)
        print(f"Found {len(products.items)} products")
        
    finally:
        await client.close()

asyncio.run(main())

Comprehensive Search & Filtering

The SDK provides powerful search and filtering capabilities that support all Akeneo API filter types with a fluent, pythonic interface.

Quick Search Examples

# Find enabled products
products = client.products.find_enabled()

# Find products in specific categories
winter_products = client.products.find_in_categories(["winter_collection"])

# Find incomplete products
incomplete = client.products.find_incomplete("ecommerce", threshold=80)

# Find recently updated products
recent = client.products.find_recently_updated(7)  # Last 7 days

# Find products by family
clothing = client.products.find_by_family(["clothing"])

Advanced Search with SearchBuilder

from myer_pim_sdk import SearchBuilder

# Complex search with multiple filters
products = client.products.search_with_builder(
    lambda f: f.enabled(True)
              .categories(["winter_collection"], "IN")
              .family(["clothing"])
              .completeness(80, "ecommerce", ">")
              .updated(30, "SINCE LAST N DAYS")
)

# Using SearchBuilder directly for more control
builder = (SearchBuilder()
           .filters(lambda f: f.enabled(True).family(["shoes"]))
           .search_locale("en_US")
           .search_scope("ecommerce")
           .pagination(page=1, limit=50))

products = client.products.search_with_builder(builder, paginated=True)
print(f"Found {len(products.items)} shoes on page {products.current_page}")

Product Property Filters

Supports all Akeneo product property filters:

# UUID-based filtering
products = client.products.find_by_uuid(["uuid1", "uuid2"])

# Category filters with operators
products = client.products.search_with_builder(
    lambda f: f.categories(["winter"], "IN")
              .categories(["clearance"], "NOT IN")
)

# Completeness filters
products = client.products.search_with_builder(
    lambda f: f.completeness(100, "ecommerce", "GREATER OR EQUALS THAN ON ALL LOCALES",
                            ["en_US", "fr_FR"])
)

# Date-based filters
products = client.products.search_with_builder(
    lambda f: f.created(["2024-01-01 00:00:00", "2024-12-31 23:59:59"], "BETWEEN")
              .updated(7, "SINCE LAST N DAYS")
)

# Parent/variant relationships
variants = client.products.find_variants_of("product_model_code")
simple_products = client.products.find_simple_products()

# Quality score filtering
low_quality = client.products.find_with_quality_score(["C", "D", "E"], "ecommerce", "en_US")

Attribute-Based Filtering

Filter by any product attribute with type-specific operators:

# Text attributes
products = client.products.search_with_builder(
    lambda f: f.attribute_text("description", "premium", "CONTAINS", "en_US", "ecommerce")
              .attribute_text("brand", "Nike", "=")
)

# Select attributes (simple/multi-select)
products = client.products.search_with_builder(
    lambda f: f.attribute_select("color", ["red", "blue"], "IN")
              .attribute_select("size", ["XL"], "NOT IN")
)

# Numeric attributes
products = client.products.search_with_builder(
    lambda f: f.attribute_number("price", 100, ">")
              .attribute_number("weight", 5.0, "<=")
)

# Boolean attributes
products = client.products.search_with_builder(
    lambda f: f.attribute_boolean("is_featured", True)
)

# Date attributes
products = client.products.search_with_builder(
    lambda f: f.attribute_date("release_date", "2024-01-01", ">")
)

# Empty/not empty attributes
products = client.products.search_with_builder(
    lambda f: f.attribute_empty("description", True, "en_US", "ecommerce")
)

Product Model Search

Product models have their own specialized search methods:

# Find by identifier
models = client.product_models.find_by_identifier(["model1", "model2"])

# Find root vs sub product models
root_models = client.product_models.find_root_models()
sub_models = client.product_models.find_sub_models(["parent1", "parent2"])

# Completeness for product models
complete_models = client.product_models.find_complete("ecommerce", locale="en_US")
incomplete_models = client.product_models.find_incomplete("ecommerce")

# Myer-specific: Find models ready for enrichment
enrichment_ready = client.product_models.find_for_enrichment("image", 10)

Pagination

Supports Akeneo's pagination format with full control:

# Basic pagination
page1 = client.products.search_with_builder(
    lambda f: f.enabled(True),
    paginated=True
)
print(f"Page {page1.current_page}: {len(page1.items)} items")
print(f"Has next: {page1.has_next}")
print(f"Next URL: {page1.next_href}")

# Manual pagination
builder = SearchBuilder().filters(lambda f: f.family(["clothing"])).limit(20)
page_num = 1

while True:
    builder.page(page_num)
    page = client.products.search_with_builder(builder, paginated=True)
    
    # Process page.items
    
    if not page.has_next:
        break
    page_num += 1

# Iterator-style pagination
for product in client.products.paginate(family=["shoes"], limit=100):
    # Process each product
    pass

Asynchronous Search

All search methods have async equivalents:

# Async search
products = await client.products.find_enabled_async()
models = await client.product_models.find_by_family_async(["clothing"])

# Async complex search
products = await client.products.search_with_builder_async(
    lambda f: f.enabled(True).completeness(90, "ecommerce", ">"),
    paginated=True
)

# Parallel async searches
results = await asyncio.gather(
    client.products.find_enabled_async(),
    client.products.find_incomplete_async("ecommerce", 90),
    client.product_models.find_root_models_async()
)

Raw Search (Advanced)

For complete control, use raw search criteria:

# Raw search criteria
search_criteria = {
    "enabled": [{"operator": "=", "value": True}],
    "family": [{"operator": "IN", "value": ["clothing"]}],
    "completeness": [{"operator": ">", "value": 80, "scope": "ecommerce"}]
}
products = client.products.search(search_criteria)

# Mix raw and builder patterns
builder = (SearchBuilder()
           .raw_filter("enabled", "=", True)
           .raw_filter("categories", "IN", ["winter_collection"])
           .filters(lambda f: f.attribute_text("brand", "Nike")))

products = client.products.search_with_builder(builder)

Myer-Specific Search Workflows

# Find products ready for image enrichment
image_ready = client.product_models.search_with_builder(
    lambda f: f.attribute_number("image_status", 10)  # Status 10 = ready
)

# Find products with copy enrichment complete
copy_complete = client.product_models.search_with_builder(
    lambda f: f.attribute_number("copy_status", 20)  # Status 20 = complete
)

# Complex enrichment workflow
enrichment_candidates = client.product_models.search_with_builder(
    lambda f: f.attribute_number("image_status", 10)
              .attribute_empty("description", True, "en_US", "ecommerce")
              .family(["clothing", "shoes"])
              .categories(["new_arrivals"], "IN")
)

# Find supplier products needing attention
supplier_products = client.products.search_with_builder(
    lambda f: f.categories(["supplier_category_1"], "IN")
              .completeness(90, "ecommerce", "<")
              .enabled(True)
)

Magic Search for Product Values

The SDK includes powerful "magic" search capabilities specifically designed for Myer's complex product attribute structure, making it easy to search by any product value with minimal code.

Generic Attribute Search

The by_attribute() method automatically detects value types and applies appropriate operators:

# Generic search by any attribute - the "magic" method
products = client.product_models.search_with_builder(
    lambda f: f.by_attribute("supplier_style", "FI02847")
              .by_attribute("copy_status", "20")
              .by_attribute("concession", True)
              .by_attribute("days_in_stock", 1000, ">")
)

Myer-Specific Convenience Methods

Pre-built methods for common Myer product attributes:

# Myer-specific convenience methods
products = client.product_models.search_with_builder(
    lambda f: f.supplier_style("FI02847")  # Supplier style search
              .brand(["Oxford", "Nike"])  # Multiple brands
              .online_name("Belt", "CONTAINS")  # Search in product names
              .copy_status("20")  # Copy enrichment status
              .image_status("10")  # Image enrichment status
              .supplier_trust_level(["gold", "silver"])  # Trust levels
              .online_category("women_accessories")
              .concession(True)  # Concession products
              .online_ind(True)  # Available online
              .buyable_ind(True)  # Buyable products
)

Quick Search Functions

Pre-configured search builders for common patterns:

from myer_pim_sdk.search import (
    by_supplier_style, by_brand, ready_for_enrichment,
    enrichment_complete, missing_images, online_products
)

# Quick searches
products = client.product_models.search_with_builder(by_supplier_style("FI02847"))
models = client.product_models.search_with_builder(by_brand("Oxford"))
ready = client.product_models.search_with_builder(ready_for_enrichment("image", 10))
complete = client.product_models.search_with_builder(enrichment_complete("copy", 20))
missing = client.product_models.search_with_builder(missing_images(1))
online = client.product_models.search_with_builder(online_products())

Value Filtering

Filter response values to only return specific attributes, locales, or scopes:

# Only return specific attributes to reduce response size
products = client.product_models.search_with_builder(
    SearchBuilder()
    .filters(lambda f: f.brand("Trenery"))
    .attributes(["supplier_style", "online_name", "brand", "copy_status"])
    .locales(["en_AU"])  # Only Australian English
    .scope("ecommerce")  # Only ecommerce scope
    .limit(50)
)

# Response will only contain the specified attributes/locales/scope
print(f"Attributes returned: {list(products[0].values.keys())}")

Complex Myer Workflows

Real-world enrichment and operational scenarios:

# Find products needing comprehensive enrichment
enrichment_candidates = client.product_models.search_with_builder(
    lambda f: f.copy_status("20")  # Copy complete
              .image_status("10")  # Ready for images
              .supplier_trust_level(["gold", "silver"])  # Trusted suppliers
              .online_ind(True)  # Available online
              .missing_images(1)  # Missing images
              .has_description()  # Has description
)

# Witchery women's accessories needing attention
witchery_womens = client.product_models.search_with_builder(
    lambda f: f.brand("Witchery")
              .online_department("women")
              .online_category("women_accessories")
              .copy_status("10")  # Ready for copy
              .missing_description()  # No description yet
)

# High-priority enrichment queue
high_priority = client.product_models.search_with_builder(
    lambda f: f.online_ind(True)
              .buyable_ind(True)
              .concession(False)  # Direct Myer products
              .clearance_ind(False)  # Not clearance
              .supplier_trust_level(["gold"])  # Gold suppliers only
              .copy_status("10")
)

# Audit enrichment discrepancies
discrepancies = client.product_models.search_with_builder(
    lambda f: f.copy_status("20")  # Copy supposedly complete
              .myer_copy_status("10")  # But Myer status disagrees
              .online_ind(True)
)

Supplier Analysis

# Analyze performance by supplier trust level
for level in ["gold", "silver", "bronze"]:
    count = len(client.product_models.search_with_builder(
        lambda f: f.supplier_trust_level([level])
                  .copy_status("20")
                  .image_status("20")
    ))
    print(f"{level.title()} suppliers: {count} products fully enriched")

# Find specific supplier's products
supplier_products = client.product_models.search_with_builder(
    lambda f: f.supplier("9000395")
              .copy_status("10")  # Ready for enrichment
              .online_ind(True)
)

Image and Content Management

# Products missing specific images
products_no_image1 = client.product_models.search_with_builder(
    lambda f: f.missing_images(1)  # Missing image 1
              .online_ind(True)
              .buyable_ind(True)
)

# Products with images but missing descriptions
images_no_copy = client.product_models.search_with_builder(
    lambda f: f.has_images(1)  # Has at least image 1
              .missing_description()  # But no description
              .online_ind(True)
)

# Ready for final review
ready_for_review = client.product_models.search_with_builder(
    lambda f: f.copy_status("20")  # Copy complete
              .image_status("20")  # Images complete
              .has_description()  # Has description
              .has_images(1)  # Has images
              .online_ind(True)
)

Async Magic Search

# All magic search methods have async equivalents
products = await client.product_models.search_with_builder_async(
    lambda f: f.supplier_style("FI02847")
              .copy_status("20")
)

# Parallel searches for dashboard
results = await asyncio.gather(
    client.product_models.search_with_builder_async(
        lambda f: f.copy_status("10")  # Copy queue
    ),
    client.product_models.search_with_builder_async(
        lambda f: f.image_status("10")  # Image queue
    ),
    client.product_models.search_with_builder_async(
        lambda f: f.copy_status("20").image_status("20")  # Complete
    )
)
copy_queue, image_queue, complete = results

Available Magic Methods

Product Value Searches:

  • supplier_style() - Search by supplier style code
  • brand() - Search by brand name
  • online_name() - Search in product names
  • supplier() - Search by supplier code
  • supplier_colour() - Search by supplier color
  • product_type() - Search by product type
  • online_category() - Search by online category
  • online_department() - Search by department

Status Searches:

  • copy_status() - Copy enrichment status
  • image_status() - Image enrichment status
  • myer_copy_status() - Myer copy status
  • myer_image_status() - Myer image status
  • supplier_trust_level() - Supplier trust level

Boolean Filters:

  • concession() - Concession products
  • online_ind() - Online indicator
  • buyable_ind() - Buyable indicator
  • clearance_ind() - Clearance indicator

Content Filters:

  • has_images() - Products with specific images
  • missing_images() - Products missing images
  • has_description() - Products with descriptions
  • missing_description() - Products missing descriptions

Generic:

  • by_attribute() - Search any attribute with auto-type detection

Key Concepts for Myer's System

Product Hierarchy

In Myer's Akeneo implementation:

  • Product Models (Level 1): Main entities where copy and image enrichment occurs
  • Products (Level 2): SKU-level items that inherit from product models

Enrichment Status

The SDK supports Myer's enrichment status workflow:

# Update enrichment status for a product model
client.product_models.update_enrichment_status(
    code="product_model_code",
    status_type="image", 
    status_value=10  # Ready for enrichment
)

# After enrichment is complete
client.product_models.update_enrichment_status(
    code="product_model_code",
    status_type="image",
    status_value=20  # Enrichment complete
)

Image Upload (Core Myer Workflow)

# Upload image for a product model (recommended approach)
media_file = client.media_files.upload_for_product_model(
    product_model_code="700000540",
    attribute_code="new_image1", 
    file_path="/path/to/image.jpg"
)

# Update image status after upload
client.product_models.update_enrichment_status(
    code="700000540",
    status_type="image",
    status_value=20
)

Core Resources

Products

# Get product by UUID or identifier
product = client.products.get_by_uuid("uuid-here")
product = client.products.get_by_identifier("SKU123")

# Create a new product
new_product = client.products.create_with_uuid({
    "identifier": "NEW_SKU",
    "family": "clothing",
    "values": {
        "name": [{"data": "New Product", "locale": "en_US", "scope": None}]
    }
})

# Bulk update products
results = client.products.bulk_update([
    {"identifier": "SKU1", "values": {...}},
    {"identifier": "SKU2", "values": {...}}
])

# Search products
products = client.products.search({
    "family": [{"operator": "IN", "value": ["shoes", "bags"]}]
})

Product Models

# Get product model by code
product_model = client.product_models.get_by_code("model_code")

# Create product model
new_model = client.product_models.create_product_model({
    "code": "new_model",
    "family_variant": "clothing_material_size",
    "values": {
        "description": [{"data": "Model description", "locale": "en_US", "scope": "ecommerce"}]
    }
})

# Update product model
updated_model = client.product_models.update_by_code("model_code", {
    "values": {
        "name": [{"data": "Updated Name", "locale": "en_US", "scope": None}]
    }
})

Media Files

# Upload image for product model (Myer's main use case)
media_file = client.media_files.upload_for_product_model(
    product_model_code="700000540",
    attribute_code="new_image1",
    file_path="/path/to/image.jpg"
)

# Upload image for specific product
media_file = client.media_files.upload_for_product(
    product_identifier="SKU123",
    attribute_code="image", 
    file_path="/path/to/image.jpg",
    scope="ecommerce"
)

# Download media file
binary_data = client.media_files.download("media_file_code")

# Get media file info
file_info = client.media_files.get_file_info("media_file_code")

Families and Attributes

# Get family details
family = client.families.get_by_code("clothing")

# List attributes
attributes = client.attributes.list(limit=100)

# Create new attribute
new_attribute = client.attributes.create_attribute({
    "code": "new_attribute",
    "type": "pim_catalog_text",
    "group": "marketing",
    "labels": {"en_US": "New Attribute"}
})

# Get family variants
variants = client.family_variants.list_for_family("clothing")

Categories

# Get category
category = client.categories.get_by_code("men_shoes")

# Create category
new_category = client.categories.create_category({
    "code": "new_category",
    "parent": "master",
    "labels": {"en_US": "New Category"}
})

# Upload category media
client.categories.create_media_file(
    category_code="men_shoes",
    attribute_code="category_image",
    file_path="/path/to/category_image.jpg"
)

Advanced Features

Pagination

The SDK now fully supports Akeneo's pagination format with _links, current_page, and _embedded.items:

# Manual pagination with full Akeneo support
page1 = client.products.list_by_uuid(page=1, limit=100, paginated=True)
print(f"Current page: {page1.current_page}")
print(f"Has next: {page1.has_next}")
print(f"Next URL: {page1.next_href}")
print(f"Items on page: {len(page1.items)}")

if page1.has_next:
    page2 = client.products.list_by_uuid(page=2, limit=100, paginated=True)

# Access pagination URLs directly
print(f"First page: {page1.first_href}")
print(f"Previous page: {page1.previous_href}")
print(f"Self page: {page1.self_href}")
print(f"Last page: {page1.last_href}")

# Auto-pagination generator
for product in client.products.paginate(limit=100):
    print(f"Processing product: {product.identifier}")

Pagination Response Structure

The PaginatedResponse object matches Akeneo's API format exactly:

# Properties available on PaginatedResponse
response = client.families.list(paginated=True, limit=10)

# Basic pagination info
response.current_page      # Current page number
response.has_next         # True if next page exists
response.has_previous     # True if previous page exists  
response.has_first        # True if first page link exists
response.has_last         # True if last page link exists
response.items            # List of resource instances

# Direct URL access (matching Akeneo's _links structure)
response.next_href        # URL for next page
response.previous_href    # URL for previous page
response.first_href       # URL for first page
response.last_href        # URL for last page
response.self_href        # URL for current page
response.links            # Full _links object from Akeneo

# Standard list operations
len(response)             # Number of items on current page
response[0]               # First item
for item in response:     # Iterate over items
    process(item)

Bulk Operations

# Bulk update products with status tracking
products_to_update = [
    {"identifier": "SKU1", "values": {"name": [...]}},
    {"identifier": "SKU2", "values": {"description": [...]}}
]

results = client.products.bulk_update(products_to_update)
for result in results:
    if result['status_code'] == 204:
        print(f"Successfully updated {result['identifier']}")
    else:
        print(f"Failed to update {result['identifier']}: {result['message']}")

Error Handling

from myer_pim_sdk import (
    AkeneoAPIError,
    AuthenticationError, 
    ValidationError,
    NotFoundError,
    RateLimitError
)

try:
    product = client.products.get_by_identifier("INVALID_SKU")
except NotFoundError:
    print("Product not found")
except ValidationError as e:
    print(f"Validation error: {e.message}")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after} seconds")
except AkeneoAPIError as e:
    print(f"API error: {e.message}")

System Information

# Check API health
is_healthy = client.system.check_health()

# Get system info
info = client.system.get_system_information()
print(f"PIM Version: {info['version']}, Edition: {info['edition']}")

# List all available endpoints
endpoints = client.system.get_endpoints()

Configuration

Rate Limiting

The SDK automatically handles Myer's rate limit of 20 calls per minute:

from myer_pim_sdk import AkeneoClient

# Rate limiting is automatically configured for Myer's limits
client = AkeneoClient(
    client_id="client_id",
    client_secret="client_secret",
    base_url="https://your-pim.akeneo.com",
    # Rate limiting is handled automatically
)

Custom Configuration

client = AkeneoClient(
    client_id="client_id",
    client_secret="client_secret", 
    base_url="https://your-pim.akeneo.com",
    timeout=120.0,  # Request timeout in seconds
    max_retries=3,  # Number of retries on failure
    token_buffer_seconds=300  # Refresh token 5 minutes before expiry
)

Myer-Specific Workflows

Complete Product Enrichment Workflow

# 1. Get products with enrichment status 10 (ready for enrichment)
products = client.product_models.list(
    search='{"enrichment_status":[{"operator":"=","value":"10"}]}'
)

for product_model in products:
    try:
        # 2. Upload product images
        for i in range(1, 6):  # Upload up to 5 images
            try:
                image_path = f"/images/{product_model.code}_image_{i}.jpg"
                client.media_files.upload_for_product_model(
                    product_model_code=product_model.code,
                    attribute_code=f"new_image{i}",
                    file_path=image_path
                )
            except FileNotFoundError:
                break  # No more images for this product
        
        # 3. Update copy/attributes
        client.product_models.update_by_code(product_model.code, {
            "values": {
                "description": [{
                    "data": "Enhanced product description",
                    "locale": "en_US", 
                    "scope": "ecommerce"
                }],
                "short_description": [{
                    "data": "Short description",
                    "locale": "en_US",
                    "scope": "ecommerce"  
                }]
            }
        })
        
        # 4. Set enrichment status to 20 (complete)
        client.product_models.update_enrichment_status(
            code=product_model.code,
            status_type="image",
            status_value=20
        )
        
        print(f"Successfully enriched {product_model.code}")
        
    except Exception as e:
        print(f"Failed to enrich {product_model.code}: {e}")

Batch Image Upload

import os
from pathlib import Path

def upload_images_batch(client, image_directory: str, product_codes: list):
    """Upload images for multiple product models in batch."""
    
    results = []
    
    for product_code in product_codes:
        product_results = {"code": product_code, "uploaded_images": []}
        
        # Look for images matching the product code
        image_patterns = [
            f"{product_code}_*.jpg",
            f"{product_code}_*.png", 
            f"{product_code}_*.jpeg"
        ]
        
        image_files = []
        for pattern in image_patterns:
            image_files.extend(Path(image_directory).glob(pattern))
        
        # Upload each image found
        for i, image_path in enumerate(image_files[:5], 1):  # Max 5 images
            try:
                media_file = client.media_files.upload_for_product_model(
                    product_model_code=product_code,
                    attribute_code=f"new_image{i}",
                    file_path=str(image_path)
                )
                product_results["uploaded_images"].append({
                    "attribute": f"new_image{i}",
                    "file": image_path.name,
                    "media_code": media_file.code
                })
            except Exception as e:
                print(f"Failed to upload {image_path} for {product_code}: {e}")
        
        # Update enrichment status if any images were uploaded
        if product_results["uploaded_images"]:
            try:
                client.product_models.update_enrichment_status(
                    code=product_code,
                    status_type="image", 
                    status_value=20
                )
                product_results["status"] = "completed"
            except Exception as e:
                print(f"Failed to update status for {product_code}: {e}")
                product_results["status"] = "uploaded_but_status_failed"
        else:
            product_results["status"] = "no_images_found"
        
        results.append(product_results)
    
    return results

# Usage
image_results = upload_images_batch(
    client=client,
    image_directory="/path/to/images",
    product_codes=["700000540", "700000541", "700000542"]
)

Testing

# Install development dependencies
pip install -e .[dev]

# Run tests
pytest

# Run with coverage
pytest --cov=myer_pim_sdk

# Type checking
mypy myer_pim_sdk/

# Code formatting
black myer_pim_sdk/
isort myer_pim_sdk/

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass
  6. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For issues and questions:

Changelog

1.0.0 (2025-07-09)

  • Initial release
  • Full Akeneo REST API support
  • Synchronous and asynchronous clients
  • Rate limiting for Myer's API constraints
  • Comprehensive media file handling
  • Bulk operations support
  • Type-safe models with Pydantic
  • Automatic OAuth2 token management
  • Easy + Magic search system for easily finding specific products

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

myer_pim_sdk-0.1.2.tar.gz (43.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

myer_pim_sdk-0.1.2-py3-none-any.whl (51.8 kB view details)

Uploaded Python 3

File details

Details for the file myer_pim_sdk-0.1.2.tar.gz.

File metadata

  • Download URL: myer_pim_sdk-0.1.2.tar.gz
  • Upload date:
  • Size: 43.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.2 CPython/3.13.3 Darwin/24.5.0

File hashes

Hashes for myer_pim_sdk-0.1.2.tar.gz
Algorithm Hash digest
SHA256 a6c5b9a50fc0da92beb5b954da0def531d0690f4d7ae48bc9e89303cbcd75a74
MD5 5dbf38c786afc1d2c9a27d3a2612bc19
BLAKE2b-256 d7b161fec732cca09e6315809386ab95e543c3a4717079dcbaccdb9c35a44bb9

See more details on using hashes here.

File details

Details for the file myer_pim_sdk-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: myer_pim_sdk-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 51.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.2 CPython/3.13.3 Darwin/24.5.0

File hashes

Hashes for myer_pim_sdk-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0ea21702978a8d94dad143f1822fabc2da4fa78e798988c507a4dd5fd7901dc8
MD5 a87fef5cdcea10e3a3db812e2016f06d
BLAKE2b-256 431e080114633fad4da31ac1d2eb9c19e82317dda9ffb9335e5744384c9afba0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page