starlings


Namestarlings JSON
Version 0.2.2 PyPI version JSON
download
home_pageNone
SummaryA Python package for comparing entity resolutions from different processes
upload_time2025-09-09 18:32:06
maintainerNone
docs_urlNone
authorWill Langdale
requires_python>=3.10
licenseNone
keywords entity-resolution data-processing rust
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Starlings

<img src="./docs/assets/starling-logo.png" alt="The Starlings logo" width="300" />

**High-performance entity resolution evaluation for Python**

Starlings treats entities as what they truly are: collections of records across multiple datasets. Compare entity resolution methods efficiently using set operations rather than pairwise record comparisons.

## Quickstart

```python
import starlings as sl

# Your entity resolution results from two different methods
splink_results = [
    {"customers": ["cust_001", "cust_002"], "transactions": ["txn_100"]},
    {"customers": ["cust_003"], "transactions": ["txn_101", "txn_102"]},
]

dedupe_results = [
    {"customers": ["cust_001"], "transactions": ["txn_100"]},
    {"customers": ["cust_002", "cust_003"], "transactions": ["txn_101", "txn_102"]},
]

# Create frame and declare datasets upfront for efficiency
frame = sl.EntityFrame()
frame.declare_dataset("customers")
frame.declare_dataset("transactions")

# Add both collections
frame.add_method("splink", splink_results)
frame.add_method("dedupe", dedupe_results)

# Calculate hashes for integrity and comparison
frame.splink.add_hash("blake3")
frame.dedupe.add_hash("blake3")

# Compare how well the methods agree
comparisons = frame.compare_collections("splink", "dedupe")
avg_similarity = sum(c['jaccard'] for c in comparisons) / len(comparisons)
print(f"Average agreement: {avg_similarity:.2f}")
```

## What is starlings?

Traditional entity resolution evaluation compares pairs of records. starlings compares **entities as complete objects** - collections of records that span multiple datasets.

Instead of asking "do these two records match?", starlings asks "how well do these two methods agree on what constitutes this entity?"

## Core concepts

* `Entity`: A collection of record IDs across multiple datasets

```python
# Entity representing "Michael Smith"
{
    "customers": ["cust_001", "cust_045"],
    "transactions": ["txn_555", "txn_777"], 
    "addresses": ["addr_123"]
}
```

* `EntityCollection`: Entities from one method (like pandas Series)  
* `EntityFrame`: Multiple collections for comparison (like pandas DataFrame)

## Installation

```shell
# From PyPI (when available)
pip install entityframe

# From source
git clone https://github.com/yourusername/entityframe
cd entityframe
just install && just build
```

## Working with hashes and metadata

```python
import starlings as sl

# Method 1: Conservative clustering
method1 = [
    {"customers": ["john_1"], "emails": ["john@email.com"]},
    {"customers": ["john_2"], "emails": ["j.smith@work.com"]},
]

# Method 2: Aggressive clustering  
method2 = [
    {"customers": ["john_1", "john_2"], "emails": ["john@email.com", "j.smith@work.com"]},
]

# Create frame and declare datasets upfront for efficiency
frame = sl.EntityFrame()
frame.declare_dataset("customers")
frame.declare_dataset("emails")

# Add the collections
frame.add_method("conservative", method1)
frame.add_method("aggressive", method2)

# Calculate hashes for entity integrity (batch processed for performance)
frame.conservative.add_hash("blake3")
frame.aggressive.add_hash("blake3")

# Access entities with metadata
entity = frame.conservative[0]
print(f"Entity: {entity}")
# {'metadata': {'hash': b'\x7a\x2f\x8c\x91...'}, 'customers': ['john_1'], 'emails': ['john@email.com']}

# Verify hash integrity later
if frame.conservative.verify_hashes("blake3"):
    print("All hashes verified successfully")

# See how methods differ
results = frame.compare_collections("conservative", "aggressive")
for result in results:
    print(f"Entity {result['entity_index']}: {result['jaccard']:.2f} similarity")
```

## Working with pre-existing metadata

```python
import starlings as sl

# Entities with existing metadata (from previous processing)
existing_entities = [
    {
        "customers": ["alice_1", "alice_2"], 
        "emails": ["alice@personal.com", "a.johnson@company.com"],
        "metadata": {
            "id": 1,
            "hash": b"\x3b\x4c\x9d\xa2\x5f\x7e\x8b\x1c",
            "created_at": "2024-01-15T10:30:00Z",
            "source": "import_batch_001"
        }
    },
    {
        "customers": ["bob_smith"],
        "emails": ["bob.smith@email.org"],
        "metadata": {
            "id": 2,
            "hash": b"\x9e\x2a\x7f\x3c\x8d\x1b\x6e\x4a",
            "created_at": "2024-01-15T10:31:00Z", 
            "source": "import_batch_001"
        }
    }
]

# Create frame and add method with existing entities
frame = sl.EntityFrame()
frame.declare_dataset("customers")
frame.declare_dataset("emails")
frame.add_method("imported", existing_entities)

# Access entity with preserved metadata
entity = frame.imported[0]
print(f"ID: {entity['metadata']['id']}")
print(f"Hash: {entity['metadata']['hash'].hex()}")
print(f"Source: {entity['metadata']['source']}")

# Verify existing hashes match the data
if frame.imported.verify_hashes("blake3"):
    print("All imported hashes verified successfully")
```

## Working with individual entities

```python
# Create an entity manually
entity = ef.Entity()
entity.add_records("customers", [1, 2, 3])
entity.add_records("transactions", [100, 101])

# Query the entity
print(f"Total records: {entity.total_records()}")
print(f"Customer records: {entity.get_records('customers')}")
print(f"Has transactions: {entity.has_dataset('transactions')}")

# Compare two entities
other_entity = ef.Entity()
other_entity.add_records("customers", [2, 3, 4])
similarity = entity.jaccard_similarity(other_entity)
print(f"Jaccard similarity: {similarity:.2f}")
```

## Working with multiple methods

starlings makes it simple to compare different entity resolution approaches:

```python
frame = sl.EntityFrame()

# Add results from different methods
frame.add_method("splink", splink_results)
frame.add_method("dedupe", dedupe_results) 
frame.add_method("custom_rules", rules_results)

# Compare any two methods
comparisons = frame.compare_collections("splink", "dedupe")
print(f"Methods agree on {len([c for c in comparisons if c['jaccard'] > 0.8])} entities")

# See what methods you have
print(f"Methods: {frame.get_collection_names()}")
print(f"Total entities per method: {frame.total_entities() // len(frame.get_collection_names())}")
```

## API overview

### `EntityFrame`

```python
frame = sl.EntityFrame()
frame = sl.EntityFrame.with_datasets(["ds1", "ds2"])  # Pre-declare datasets (optional)
frame.add_method(name, entity_data)              # Add method results 
frame.add_collection(name, collection)           # Add pre-built collection  
frame.compare_collections(name1, name2)          # Compare two methods
frame.get_collection(name)                       # Get specific collection
frame.get_collection_names()                     # List all methods
frame.total_entities()                           # Count total entities
frame.entity_has_dataset(method, index, name)    # Check if entity has dataset
```

### `EntityCollection`

```python
collection = ef.EntityCollection("method_name")
collection.get_entity(index)                     # Get entity by index
collection.get_entities()                        # Get all entities
collection.len()                                 # Number of entities
collection.compare_with(other_collection)        # Compare with another collection
collection.total_records()                       # Count all records
collection.is_empty()                            # Check if empty
```

### `Entity`

```python
entity = ef.Entity()
entity.add_record(dataset, record_id)           # Add single record
entity.add_records(dataset, record_ids)         # Add multiple records
entity.get_records(dataset)                     # Get records for dataset
entity.jaccard_similarity(other_entity)         # Compare entities
entity.total_records()                          # Count all records
```

## Data format

starlings expects your entity resolution results as a list of dictionaries:

```python
entity_data = [
    {
        "dataset1": ["record_1", "record_2"],    # Records from dataset1
        "dataset2": ["record_10"],               # Records from dataset2
    },
    {
        "dataset1": ["record_3"],
        "dataset3": ["record_20", "record_21"],  # Different datasets per entity
    },
    # ... more entities
]
```

## Performance

starlings is built for scale:

- **Memory efficient**: Handles millions of entities without breaking a sweat
- **Fast comparisons**: Optimised set operations for entity comparison
- **Rust powered**: High-performance core with Python convenience

Handle millions of entities efficiently.

## Development

```bash
just install    # Install dependencies
just build      # Build Rust extension  
just test       # Run all tests
just format     # Format code
```

## License

MIT License

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "starlings",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "entity-resolution, data-processing, rust",
    "author": "Will Langdale",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/d3/e8/d747ab15d14f450e038f0c6e4ca3027cda992607b638f593600c50ca9b8d/starlings-0.2.2.tar.gz",
    "platform": null,
    "description": "# Starlings\n\n<img src=\"./docs/assets/starling-logo.png\" alt=\"The Starlings logo\" width=\"300\" />\n\n**High-performance entity resolution evaluation for Python**\n\nStarlings treats entities as what they truly are: collections of records across multiple datasets. Compare entity resolution methods efficiently using set operations rather than pairwise record comparisons.\n\n## Quickstart\n\n```python\nimport starlings as sl\n\n# Your entity resolution results from two different methods\nsplink_results = [\n    {\"customers\": [\"cust_001\", \"cust_002\"], \"transactions\": [\"txn_100\"]},\n    {\"customers\": [\"cust_003\"], \"transactions\": [\"txn_101\", \"txn_102\"]},\n]\n\ndedupe_results = [\n    {\"customers\": [\"cust_001\"], \"transactions\": [\"txn_100\"]},\n    {\"customers\": [\"cust_002\", \"cust_003\"], \"transactions\": [\"txn_101\", \"txn_102\"]},\n]\n\n# Create frame and declare datasets upfront for efficiency\nframe = sl.EntityFrame()\nframe.declare_dataset(\"customers\")\nframe.declare_dataset(\"transactions\")\n\n# Add both collections\nframe.add_method(\"splink\", splink_results)\nframe.add_method(\"dedupe\", dedupe_results)\n\n# Calculate hashes for integrity and comparison\nframe.splink.add_hash(\"blake3\")\nframe.dedupe.add_hash(\"blake3\")\n\n# Compare how well the methods agree\ncomparisons = frame.compare_collections(\"splink\", \"dedupe\")\navg_similarity = sum(c['jaccard'] for c in comparisons) / len(comparisons)\nprint(f\"Average agreement: {avg_similarity:.2f}\")\n```\n\n## What is starlings?\n\nTraditional entity resolution evaluation compares pairs of records. starlings compares **entities as complete objects** - collections of records that span multiple datasets.\n\nInstead of asking \"do these two records match?\", starlings asks \"how well do these two methods agree on what constitutes this entity?\"\n\n## Core concepts\n\n* `Entity`: A collection of record IDs across multiple datasets\n\n```python\n# Entity representing \"Michael Smith\"\n{\n    \"customers\": [\"cust_001\", \"cust_045\"],\n    \"transactions\": [\"txn_555\", \"txn_777\"], \n    \"addresses\": [\"addr_123\"]\n}\n```\n\n* `EntityCollection`: Entities from one method (like pandas Series)  \n* `EntityFrame`: Multiple collections for comparison (like pandas DataFrame)\n\n## Installation\n\n```shell\n# From PyPI (when available)\npip install entityframe\n\n# From source\ngit clone https://github.com/yourusername/entityframe\ncd entityframe\njust install && just build\n```\n\n## Working with hashes and metadata\n\n```python\nimport starlings as sl\n\n# Method 1: Conservative clustering\nmethod1 = [\n    {\"customers\": [\"john_1\"], \"emails\": [\"john@email.com\"]},\n    {\"customers\": [\"john_2\"], \"emails\": [\"j.smith@work.com\"]},\n]\n\n# Method 2: Aggressive clustering  \nmethod2 = [\n    {\"customers\": [\"john_1\", \"john_2\"], \"emails\": [\"john@email.com\", \"j.smith@work.com\"]},\n]\n\n# Create frame and declare datasets upfront for efficiency\nframe = sl.EntityFrame()\nframe.declare_dataset(\"customers\")\nframe.declare_dataset(\"emails\")\n\n# Add the collections\nframe.add_method(\"conservative\", method1)\nframe.add_method(\"aggressive\", method2)\n\n# Calculate hashes for entity integrity (batch processed for performance)\nframe.conservative.add_hash(\"blake3\")\nframe.aggressive.add_hash(\"blake3\")\n\n# Access entities with metadata\nentity = frame.conservative[0]\nprint(f\"Entity: {entity}\")\n# {'metadata': {'hash': b'\\x7a\\x2f\\x8c\\x91...'}, 'customers': ['john_1'], 'emails': ['john@email.com']}\n\n# Verify hash integrity later\nif frame.conservative.verify_hashes(\"blake3\"):\n    print(\"All hashes verified successfully\")\n\n# See how methods differ\nresults = frame.compare_collections(\"conservative\", \"aggressive\")\nfor result in results:\n    print(f\"Entity {result['entity_index']}: {result['jaccard']:.2f} similarity\")\n```\n\n## Working with pre-existing metadata\n\n```python\nimport starlings as sl\n\n# Entities with existing metadata (from previous processing)\nexisting_entities = [\n    {\n        \"customers\": [\"alice_1\", \"alice_2\"], \n        \"emails\": [\"alice@personal.com\", \"a.johnson@company.com\"],\n        \"metadata\": {\n            \"id\": 1,\n            \"hash\": b\"\\x3b\\x4c\\x9d\\xa2\\x5f\\x7e\\x8b\\x1c\",\n            \"created_at\": \"2024-01-15T10:30:00Z\",\n            \"source\": \"import_batch_001\"\n        }\n    },\n    {\n        \"customers\": [\"bob_smith\"],\n        \"emails\": [\"bob.smith@email.org\"],\n        \"metadata\": {\n            \"id\": 2,\n            \"hash\": b\"\\x9e\\x2a\\x7f\\x3c\\x8d\\x1b\\x6e\\x4a\",\n            \"created_at\": \"2024-01-15T10:31:00Z\", \n            \"source\": \"import_batch_001\"\n        }\n    }\n]\n\n# Create frame and add method with existing entities\nframe = sl.EntityFrame()\nframe.declare_dataset(\"customers\")\nframe.declare_dataset(\"emails\")\nframe.add_method(\"imported\", existing_entities)\n\n# Access entity with preserved metadata\nentity = frame.imported[0]\nprint(f\"ID: {entity['metadata']['id']}\")\nprint(f\"Hash: {entity['metadata']['hash'].hex()}\")\nprint(f\"Source: {entity['metadata']['source']}\")\n\n# Verify existing hashes match the data\nif frame.imported.verify_hashes(\"blake3\"):\n    print(\"All imported hashes verified successfully\")\n```\n\n## Working with individual entities\n\n```python\n# Create an entity manually\nentity = ef.Entity()\nentity.add_records(\"customers\", [1, 2, 3])\nentity.add_records(\"transactions\", [100, 101])\n\n# Query the entity\nprint(f\"Total records: {entity.total_records()}\")\nprint(f\"Customer records: {entity.get_records('customers')}\")\nprint(f\"Has transactions: {entity.has_dataset('transactions')}\")\n\n# Compare two entities\nother_entity = ef.Entity()\nother_entity.add_records(\"customers\", [2, 3, 4])\nsimilarity = entity.jaccard_similarity(other_entity)\nprint(f\"Jaccard similarity: {similarity:.2f}\")\n```\n\n## Working with multiple methods\n\nstarlings makes it simple to compare different entity resolution approaches:\n\n```python\nframe = sl.EntityFrame()\n\n# Add results from different methods\nframe.add_method(\"splink\", splink_results)\nframe.add_method(\"dedupe\", dedupe_results) \nframe.add_method(\"custom_rules\", rules_results)\n\n# Compare any two methods\ncomparisons = frame.compare_collections(\"splink\", \"dedupe\")\nprint(f\"Methods agree on {len([c for c in comparisons if c['jaccard'] > 0.8])} entities\")\n\n# See what methods you have\nprint(f\"Methods: {frame.get_collection_names()}\")\nprint(f\"Total entities per method: {frame.total_entities() // len(frame.get_collection_names())}\")\n```\n\n## API overview\n\n### `EntityFrame`\n\n```python\nframe = sl.EntityFrame()\nframe = sl.EntityFrame.with_datasets([\"ds1\", \"ds2\"])  # Pre-declare datasets (optional)\nframe.add_method(name, entity_data)              # Add method results \nframe.add_collection(name, collection)           # Add pre-built collection  \nframe.compare_collections(name1, name2)          # Compare two methods\nframe.get_collection(name)                       # Get specific collection\nframe.get_collection_names()                     # List all methods\nframe.total_entities()                           # Count total entities\nframe.entity_has_dataset(method, index, name)    # Check if entity has dataset\n```\n\n### `EntityCollection`\n\n```python\ncollection = ef.EntityCollection(\"method_name\")\ncollection.get_entity(index)                     # Get entity by index\ncollection.get_entities()                        # Get all entities\ncollection.len()                                 # Number of entities\ncollection.compare_with(other_collection)        # Compare with another collection\ncollection.total_records()                       # Count all records\ncollection.is_empty()                            # Check if empty\n```\n\n### `Entity`\n\n```python\nentity = ef.Entity()\nentity.add_record(dataset, record_id)           # Add single record\nentity.add_records(dataset, record_ids)         # Add multiple records\nentity.get_records(dataset)                     # Get records for dataset\nentity.jaccard_similarity(other_entity)         # Compare entities\nentity.total_records()                          # Count all records\n```\n\n## Data format\n\nstarlings expects your entity resolution results as a list of dictionaries:\n\n```python\nentity_data = [\n    {\n        \"dataset1\": [\"record_1\", \"record_2\"],    # Records from dataset1\n        \"dataset2\": [\"record_10\"],               # Records from dataset2\n    },\n    {\n        \"dataset1\": [\"record_3\"],\n        \"dataset3\": [\"record_20\", \"record_21\"],  # Different datasets per entity\n    },\n    # ... more entities\n]\n```\n\n## Performance\n\nstarlings is built for scale:\n\n- **Memory efficient**: Handles millions of entities without breaking a sweat\n- **Fast comparisons**: Optimised set operations for entity comparison\n- **Rust powered**: High-performance core with Python convenience\n\nHandle millions of entities efficiently.\n\n## Development\n\n```bash\njust install    # Install dependencies\njust build      # Build Rust extension  \njust test       # Run all tests\njust format     # Format code\n```\n\n## License\n\nMIT License\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A Python package for comparing entity resolutions from different processes",
    "version": "0.2.2",
    "project_urls": null,
    "split_keywords": [
        "entity-resolution",
        " data-processing",
        " rust"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "79e6a8c98a355141a6df0fac7e1cee8069dc34747793acf19d1fee90cd0797d1",
                "md5": "92dd9e2758ab2cebaa77216be01526d5",
                "sha256": "e62e4b44f8f1a1bc30c8062021f15b383ac997b714e899a722e1f4012db9b6fe"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp310-cp310-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "92dd9e2758ab2cebaa77216be01526d5",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.10",
            "size": 505402,
            "upload_time": "2025-09-09T18:31:42",
            "upload_time_iso_8601": "2025-09-09T18:31:42.431406Z",
            "url": "https://files.pythonhosted.org/packages/79/e6/a8c98a355141a6df0fac7e1cee8069dc34747793acf19d1fee90cd0797d1/starlings-0.2.2-cp310-cp310-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "50c461061c406b42951e03bb18606e271eaeeb42442ba4532a510ac0f61c95ff",
                "md5": "715c41335692a441503a820de595e90d",
                "sha256": "93409666cb9123b86b3290361149106ab2a8502801976af3dc25412679e02d11"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp310-cp310-macosx_11_0_x86_64.whl",
            "has_sig": false,
            "md5_digest": "715c41335692a441503a820de595e90d",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.10",
            "size": 523576,
            "upload_time": "2025-09-09T18:31:44",
            "upload_time_iso_8601": "2025-09-09T18:31:44.102975Z",
            "url": "https://files.pythonhosted.org/packages/50/c4/61061c406b42951e03bb18606e271eaeeb42442ba4532a510ac0f61c95ff/starlings-0.2.2-cp310-cp310-macosx_11_0_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "1a55052705cfe02ff86964deca80803c0ac208e3be309f5edcc16e1b0c1d14d7",
                "md5": "45c0ee94b3e80797ca2936b92c2057ec",
                "sha256": "35ecb312feb6b122031825c98217260d3a13af9c0e06efbad8ac2f55fb598d3a"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "45c0ee94b3e80797ca2936b92c2057ec",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.10",
            "size": 637706,
            "upload_time": "2025-09-09T18:31:45",
            "upload_time_iso_8601": "2025-09-09T18:31:45.714455Z",
            "url": "https://files.pythonhosted.org/packages/1a/55/052705cfe02ff86964deca80803c0ac208e3be309f5edcc16e1b0c1d14d7/starlings-0.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f1a6767350c75029064960346e9f0bab758933f6972c2a191c8993075c758c9b",
                "md5": "80aec362bb39bdf4e4d318178b53705f",
                "sha256": "ef5823b6a27853a5274aeea5bfd610873ec633fa4e7a2fe247e9934c161bf5a4"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp310-cp310-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "80aec362bb39bdf4e4d318178b53705f",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.10",
            "size": 381856,
            "upload_time": "2025-09-09T18:31:47",
            "upload_time_iso_8601": "2025-09-09T18:31:47.247645Z",
            "url": "https://files.pythonhosted.org/packages/f1/a6/767350c75029064960346e9f0bab758933f6972c2a191c8993075c758c9b/starlings-0.2.2-cp310-cp310-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "19fb422fbdd0a05cab9f64e778557699691cff723768e8f002ba0d3471d73dbf",
                "md5": "d5113c7ef59205ef2bb253812fc2c492",
                "sha256": "0a858c7c47d4c83926727a33085c65f874fb81ee997f52c3148ac30a4e625539"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp311-cp311-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "d5113c7ef59205ef2bb253812fc2c492",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.10",
            "size": 505072,
            "upload_time": "2025-09-09T18:31:48",
            "upload_time_iso_8601": "2025-09-09T18:31:48.769329Z",
            "url": "https://files.pythonhosted.org/packages/19/fb/422fbdd0a05cab9f64e778557699691cff723768e8f002ba0d3471d73dbf/starlings-0.2.2-cp311-cp311-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a8e1dcfd3a9c40fb734af9060fd139d76939fb96edb970304e0b6e0ef428af3e",
                "md5": "d449b4da53e4f558e7ae47f3f2efbcca",
                "sha256": "306699cfd3253fd420e3456f124830aaf46260a92aa8f56dd344762efe881df0"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp311-cp311-macosx_11_0_x86_64.whl",
            "has_sig": false,
            "md5_digest": "d449b4da53e4f558e7ae47f3f2efbcca",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.10",
            "size": 523533,
            "upload_time": "2025-09-09T18:31:49",
            "upload_time_iso_8601": "2025-09-09T18:31:49.985453Z",
            "url": "https://files.pythonhosted.org/packages/a8/e1/dcfd3a9c40fb734af9060fd139d76939fb96edb970304e0b6e0ef428af3e/starlings-0.2.2-cp311-cp311-macosx_11_0_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5e66063500dccdae26d38ae0c840dce5eec0d4c0948bd2fbdab4e15153c4cb0d",
                "md5": "37d6f54486c9d1fbcdfa5199ec3898f1",
                "sha256": "c99fbf75d2ea62b24538243929bfff0ba32c5ccaf1ea0083cb1ad9a8a728c493"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "37d6f54486c9d1fbcdfa5199ec3898f1",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.10",
            "size": 637534,
            "upload_time": "2025-09-09T18:31:51",
            "upload_time_iso_8601": "2025-09-09T18:31:51.257924Z",
            "url": "https://files.pythonhosted.org/packages/5e/66/063500dccdae26d38ae0c840dce5eec0d4c0948bd2fbdab4e15153c4cb0d/starlings-0.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6d46f7191d9487d0f358935fa6dcbf4158faaefcd62798271f56aeffd7f9b91b",
                "md5": "de08331556a5141b395a289d5ab08534",
                "sha256": "0200a0230bc2918844bd906c617b857003ca9ae4aa16824ab88bca5234606c5c"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp311-cp311-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "de08331556a5141b395a289d5ab08534",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.10",
            "size": 381258,
            "upload_time": "2025-09-09T18:31:52",
            "upload_time_iso_8601": "2025-09-09T18:31:52.800357Z",
            "url": "https://files.pythonhosted.org/packages/6d/46/f7191d9487d0f358935fa6dcbf4158faaefcd62798271f56aeffd7f9b91b/starlings-0.2.2-cp311-cp311-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "2378d1c8eda78a52cc397e1f495f2b8ea2314259c0be6b0811b1bb446fa82fc8",
                "md5": "58e85ebe098fe3397b5fec351e0dca1a",
                "sha256": "a2f7c7395fb198fdc37394661dd4ff2d710dd7da95306c27920c8f97ac0c0d66"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp312-cp312-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "58e85ebe098fe3397b5fec351e0dca1a",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.10",
            "size": 502140,
            "upload_time": "2025-09-09T18:31:54",
            "upload_time_iso_8601": "2025-09-09T18:31:54.070270Z",
            "url": "https://files.pythonhosted.org/packages/23/78/d1c8eda78a52cc397e1f495f2b8ea2314259c0be6b0811b1bb446fa82fc8/starlings-0.2.2-cp312-cp312-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "beb28db58c8f5458c28cd53f46d3efcf8fb416fd11f4e9f26a68dbeac8ffc73a",
                "md5": "2e444cea9ce8a174f1efb621d97e3c13",
                "sha256": "bc4365f178be120a86daace4c8eb2603bf3bfe12825defc8f955351aa64c8849"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp312-cp312-macosx_11_0_x86_64.whl",
            "has_sig": false,
            "md5_digest": "2e444cea9ce8a174f1efb621d97e3c13",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.10",
            "size": 521541,
            "upload_time": "2025-09-09T18:31:55",
            "upload_time_iso_8601": "2025-09-09T18:31:55.686370Z",
            "url": "https://files.pythonhosted.org/packages/be/b2/8db58c8f5458c28cd53f46d3efcf8fb416fd11f4e9f26a68dbeac8ffc73a/starlings-0.2.2-cp312-cp312-macosx_11_0_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5d1434e8683432eb59faae0600c83278e776cdaaf12ab3b6a27e1e1f773f7518",
                "md5": "6b18d275e1ea86ff4e9345b1d25eef3b",
                "sha256": "22a74fd7d53818dc154e6aac3b0223ef8f197b50bda4607b342d6d16110a4fdb"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "6b18d275e1ea86ff4e9345b1d25eef3b",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.10",
            "size": 636828,
            "upload_time": "2025-09-09T18:31:58",
            "upload_time_iso_8601": "2025-09-09T18:31:58.109096Z",
            "url": "https://files.pythonhosted.org/packages/5d/14/34e8683432eb59faae0600c83278e776cdaaf12ab3b6a27e1e1f773f7518/starlings-0.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "3f70df669a0835b890ab10a8d3ae44ab6a525e765c9dec72666589ef1e7021c4",
                "md5": "931a73a47df665998988ae457153e30d",
                "sha256": "1526578f45541cf5a597af4197d09152bf6c6f8cb1b4c8d828b8828921923e30"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp312-cp312-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "931a73a47df665998988ae457153e30d",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.10",
            "size": 381679,
            "upload_time": "2025-09-09T18:32:00",
            "upload_time_iso_8601": "2025-09-09T18:32:00.038686Z",
            "url": "https://files.pythonhosted.org/packages/3f/70/df669a0835b890ab10a8d3ae44ab6a525e765c9dec72666589ef1e7021c4/starlings-0.2.2-cp312-cp312-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f464c827f3705d72253ef191c2e065be93a3d8590cfe4b8584fe530f4fc03d7e",
                "md5": "08243bf1b242c563e06dd9f424f66c4c",
                "sha256": "33432775ab94517f55d8a8eb88f150dbde0494e10295e4b48420ebc875de8313"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp313-cp313-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "08243bf1b242c563e06dd9f424f66c4c",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.10",
            "size": 502577,
            "upload_time": "2025-09-09T18:32:01",
            "upload_time_iso_8601": "2025-09-09T18:32:01.284929Z",
            "url": "https://files.pythonhosted.org/packages/f4/64/c827f3705d72253ef191c2e065be93a3d8590cfe4b8584fe530f4fc03d7e/starlings-0.2.2-cp313-cp313-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "ef7f616d33dd5597e010d8258f6cc086213e83e9832705d9bd8ac58b39d75be0",
                "md5": "8df7afa0ca2899dc29a4e3c09963bd20",
                "sha256": "4b41454d64b1c0812c633900deb1abcb738c38469fe6a78c855584393ade0115"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp313-cp313-macosx_11_0_x86_64.whl",
            "has_sig": false,
            "md5_digest": "8df7afa0ca2899dc29a4e3c09963bd20",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.10",
            "size": 521912,
            "upload_time": "2025-09-09T18:32:02",
            "upload_time_iso_8601": "2025-09-09T18:32:02.596527Z",
            "url": "https://files.pythonhosted.org/packages/ef/7f/616d33dd5597e010d8258f6cc086213e83e9832705d9bd8ac58b39d75be0/starlings-0.2.2-cp313-cp313-macosx_11_0_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "ec04f2bc8afc99e81251622a81b2e65a248ab5883c23456be6ee1af455a662d9",
                "md5": "d4e2c8aa5b47638fb4a92e1a5aac11a0",
                "sha256": "24eabc27ba92d42c3490d87387b9c5b9af794e0437927e8fca8cc4ab549d796e"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "d4e2c8aa5b47638fb4a92e1a5aac11a0",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.10",
            "size": 637078,
            "upload_time": "2025-09-09T18:32:03",
            "upload_time_iso_8601": "2025-09-09T18:32:03.882646Z",
            "url": "https://files.pythonhosted.org/packages/ec/04/f2bc8afc99e81251622a81b2e65a248ab5883c23456be6ee1af455a662d9/starlings-0.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "de3c9910a679d501edc4783950166e92df9cc9bd2751992bda813c06d99c83ac",
                "md5": "0bfeb1edf05f7d9023e27e4a7a988a09",
                "sha256": "0268c39bf67fb488f6ebe75edf441d7717e4af910cc29618f6debd331240e5c8"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2-cp313-cp313-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "0bfeb1edf05f7d9023e27e4a7a988a09",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.10",
            "size": 382169,
            "upload_time": "2025-09-09T18:32:05",
            "upload_time_iso_8601": "2025-09-09T18:32:05.441890Z",
            "url": "https://files.pythonhosted.org/packages/de/3c/9910a679d501edc4783950166e92df9cc9bd2751992bda813c06d99c83ac/starlings-0.2.2-cp313-cp313-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d3e8d747ab15d14f450e038f0c6e4ca3027cda992607b638f593600c50ca9b8d",
                "md5": "5df23540bcdc7ebe2fa8a999b2bddad6",
                "sha256": "4bea8b301f1332012d3ecb767136c7216963b407f7c3a3ab6c4968550111f28b"
            },
            "downloads": -1,
            "filename": "starlings-0.2.2.tar.gz",
            "has_sig": false,
            "md5_digest": "5df23540bcdc7ebe2fa8a999b2bddad6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 69119,
            "upload_time": "2025-09-09T18:32:06",
            "upload_time_iso_8601": "2025-09-09T18:32:06.870132Z",
            "url": "https://files.pythonhosted.org/packages/d3/e8/d747ab15d14f450e038f0c6e4ca3027cda992607b638f593600c50ca9b8d/starlings-0.2.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-09 18:32:06",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "starlings"
}
        
Elapsed time: 1.10162s