# 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"
}