# Unofficial Loops Python SDK
## Introduction
Python SDK for [Loops](https://loops.so), with both sync and async clients. Mirrors the public JS SDK where it makes sense.
## Installation
```bash
pip install loops-python-unofficial
```
You will need a Loops API key from your account settings.
## Quick start
### Sync
```python
from loops_unofficial import LoopsClient, APIError, RateLimitExceededError
client = LoopsClient("<YOUR_API_KEY>")
try:
resp = client.create_contact("email@provider.com")
except RateLimitExceededError as e:
print(f"Rate limit exceeded ({e.limit}/s), remaining={e.remaining}")
except APIError as e:
print(e.status_code, e.json)
```
## Handling rate limits
If you import `RateLimitExceededError` you can check for rate limit issues with your requests.
You can access details about the rate limits from the `limit` and `remaining` attributes.
### Async
```python
import asyncio
from loops_unofficial import AsyncLoopsClient
async def main():
client = AsyncLoopsClient("<YOUR_API_KEY>")
try:
resp = await client.create_contact("email@provider.com")
finally:
await client.aclose()
asyncio.run(main())
```
## Default contact properties
Each contact in Loops has a set of default properties. These will always be returned in API results.
- `id`
- `email`
- `firstName`
- `lastName`
- `source`
- `subscribed`
- `userGroup`
- `userId`
## Custom contact properties
You can use custom contact properties in API calls. Please make sure to [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.
## Methods
- test_api_key()
- create_contact(email, properties=None, mailing_lists=None)
- update_contact(email, properties, mailing_lists=None)
- find_contact(email=None, user_id=None)
- delete_contact(email=None, user_id=None)
- create*contact_property(name, type*)
- get*contact_properties(list*=None)
- get_mailing_lists()
- send_event(email=None, user_id=None, event_name=..., contact_properties=None, event_properties=None, mailing_lists=None, headers=None)
- send_transactional_email(transactional_id, email, add_to_audience=None, data_variables=None, attachments=None, headers=None)
- get_transactional_emails(per_page=None, cursor=None)
---
### testApiKey()
Test that an API key is valid.
[API Reference](https://loops.so/docs/api-reference/api-key)
#### Parameters
None
#### Example (Python)
```python
from loops_unofficial import LoopsClient
client = LoopsClient("<API_KEY>")
resp = client.test_api_key()
```
#### Response
```json
{
"success": true,
"teamName": "My team"
}
```
Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).
```json
HTTP 401 Unauthorized
{
"error": "Invalid API key"
}
```
---
### createContact()
Create a new contact.
[API Reference](https://loops.so/docs/api-reference/create-contact)
#### Parameters
| Name | Type | Required | Notes |
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email` | string | Yes | If a contact already exists with this email address, an error response will be returned. |
| `properties` | object | No | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)). |
| `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. |
#### Examples (Python)
```python
from loops_unofficial import LoopsClient
client = LoopsClient("<API_KEY>")
# Minimal
resp = client.create_contact("hello@gmail.com")
# With properties and mailing lists
contact_properties = {"firstName": "Bob", "favoriteColor": "Red"}
mailing_lists = {"cm06f5v0e45nf0ml5754o9cix": True, "cm16k73gq014h0mmj5b6jdi9r": False}
resp = client.create_contact("hello@gmail.com", contact_properties, mailing_lists)
```
#### Response
```json
{
"success": true,
"id": "id_of_contact"
}
```
Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).
```json
HTTP 400 Bad Request
{
"success": false,
"message": "An error message here."
}
```
---
### updateContact()
Update a contact.
Note: To update a contact's email address, the contact requires a `userId` value. Then you can make a request with their `userId` and an updated email address.
[API Reference](https://loops.so/docs/api-reference/update-contact)
#### Parameters
| Name | Type | Required | Notes |
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email` | string | Yes | The email address of the contact to update. If there is no contact with this email address, a new contact will be created using the email and properties in this request. |
| `properties` | object | No | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)). |
| `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. |
#### Example (Python)
```python
client.update_contact("hello@gmail.com", {"firstName": "Bob", "favoriteColor": "Blue"})
# Updating a contact's email address using userId
client.update_contact("newemail@gmail.com", {"userId": "1234"})
```
#### Response
```json
{
"success": true,
"id": "id_of_contact"
}
```
Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).
```json
HTTP 400 Bad Request
{
"success": false,
"message": "An error message here."
}
```
---
### findContact()
Find a contact.
[API Reference](https://loops.so/docs/api-reference/find-contact)
#### Parameters
You must use one parameter in the request.
| Name | Type | Required | Notes |
| -------- | ------ | -------- | ----- |
| `email` | string | No | |
| `userId` | string | No | |
#### Examples (Python)
```python
client.find_contact(email="hello@gmail.com")
client.find_contact(user_id="12345")
```
#### Response
This method will return a list containing a single contact object, which will include all default properties and any custom properties.
If no contact is found, an empty list will be returned.
```json
[
{
"id": "cll6b3i8901a9jx0oyktl2m4u",
"email": "hello@gmail.com",
"firstName": "Bob",
"lastName": null,
"source": "API",
"subscribed": true,
"userGroup": "",
"userId": "12345",
"mailingLists": {
"cm06f5v0e45nf0ml5754o9cix": true
},
"favoriteColor": "Blue" /* Custom property */
}
]
```
---
### deleteContact()
Delete a contact, either by email address or `userId`.
[API Reference](https://loops.so/docs/api-reference/delete-contact)
#### Parameters
You must use one parameter in the request.
| Name | Type | Required | Notes |
| -------- | ------ | -------- | ----- |
| `email` | string | No | |
| `userId` | string | No | |
#### Example (Python)
```python
client.delete_contact(email="hello@gmail.com")
client.delete_contact(user_id="12345")
```
#### Response
```json
{
"success": true,
"message": "Contact deleted."
}
```
Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).
```json
HTTP 400 Bad Request
{
"success": false,
"message": "An error message here."
}
```
```json
HTTP 404 Not Found
{
"success": false,
"message": "An error message here."
}
```
---
### createContactProperty()
Create a new contact property.
[API Reference](https://loops.so/docs/api-reference/create-contact-property)
#### Parameters
| Name | Type | Required | Notes |
| ------ | ------ | -------- | -------------------------------------------------------------------------------------- |
| `name` | string | Yes | The name of the property. Should be in camelCase, like `planName` or `favouriteColor`. |
| `type` | string | Yes | The property's value type.<br />Can be one of `string`, `number`, `boolean` or `date`. |
#### Examples (Python)
```python
client.create_contact_property("planName", "string")
```
#### Response
```json
{
"success": true
}
```
Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).
```json
HTTP 400 Bad Request
{
"success": false,
"message": "An error message here."
}
```
---
### getContactProperties()
Get a list of your account's contact properties.
[API Reference](https://loops.so/docs/api-reference/list-contact-properties)
#### Parameters
| Name | Type | Required | Notes |
| ------ | ------ | -------- | --------------------------------------------------------------- |
| `list` | string | No | Use "custom" to retrieve only your account's custom properties. |
#### Example (Python)
```python
client.get_contact_properties()
client.get_contact_properties("custom")
```
#### Response
This method will return a list of contact property objects containing `key`, `label` and `type` attributes.
```json
[
{
"key": "firstName",
"label": "First Name",
"type": "string"
},
{
"key": "lastName",
"label": "Last Name",
"type": "string"
},
{
"key": "email",
"label": "Email",
"type": "string"
},
{
"key": "notes",
"label": "Notes",
"type": "string"
},
{
"key": "source",
"label": "Source",
"type": "string"
},
{
"key": "userGroup",
"label": "User Group",
"type": "string"
},
{
"key": "userId",
"label": "User Id",
"type": "string"
},
{
"key": "subscribed",
"label": "Subscribed",
"type": "boolean"
},
{
"key": "createdAt",
"label": "Created At",
"type": "date"
},
{
"key": "favoriteColor",
"label": "Favorite Color",
"type": "string"
},
{
"key": "plan",
"label": "Plan",
"type": "string"
}
]
```
---
### getMailingLists()
Get a list of your account's mailing lists. [Read more about mailing lists](https://loops.so/docs/contacts/mailing-lists)
[API Reference](https://loops.so/docs/api-reference/list-mailing-lists)
#### Parameters
None
#### Example (Python)
```python
client.get_mailing_lists()
```
#### Response
This method will return a list of mailing list objects containing `id`, `name`, `description` and `isPublic` attributes.
If your account has no mailing lists, an empty list will be returned.
```json
[
{
"id": "cm06f5v0e45nf0ml5754o9cix",
"name": "Main list",
"description": "All customers.",
"isPublic": true
},
{
"id": "cm16k73gq014h0mmj5b6jdi9r",
"name": "Investors",
"description": null,
"isPublic": false
}
]
```
---
### sendEvent()
Send an event to trigger an email in Loops. [Read more about events](https://loops.so/docs/events)
[API Reference](https://loops.so/docs/api-reference/send-event)
#### Parameters
| Name | Type | Required | Notes |
| ------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email` | string | No | The contact's email address. Required if `userId` is not present. |
| `userId` | string | No | The contact's unique user ID. If you use `userId` without `email`, this value must have already been added to your contact in Loops. Required if `email` is not present. |
| `eventName` | string | Yes | |
| `contactProperties` | object | No | An object containing contact properties, which will be updated or added to the contact when the event is received.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)). |
| `eventProperties` | object | No | An object containing event properties, which will be made available in emails that are triggered by this event.<br />Values can be of type `string`, `number`, `boolean` or `date` ([see allowed date formats](https://loops.so/docs/events/properties#important-information-about-event-properties)). |
| `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. |
| `headers` | object | No | Additional headers to send with the request. |
#### Examples (Python)
```python
# Minimal
client.send_event(email="hello@gmail.com", event_name="signup")
# With event properties and mailing lists
client.send_event(
email="hello@gmail.com",
event_name="signup",
event_properties={"username": "user1234", "signupDate": "2024-03-21T10:09:23Z"},
mailing_lists={"cm06f5v0e45nf0ml5754o9cix": True, "cm16k73gq014h0mmj5b6jdi9r": False},
)
# With both email and userId and contact properties
client.send_event(
user_id="1234567890",
email="hello@gmail.com",
event_name="signup",
contact_properties={"firstName": "Bob", "plan": "pro"},
)
# With Idempotency-Key header
client.send_event(
email="hello@gmail.com",
event_name="signup",
headers={"Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000"},
)
```
#### Response
```json
{
"success": true
}
```
Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).
```json
HTTP 400 Bad Request
{
"success": false,
"message": "An error message here."
}
```
---
### sendTransactionalEmail()
Send a transactional email to a contact. [Learn about sending transactional email](https://loops.so/docs/transactional/guide)
[API Reference](https://loops.so/docs/api-reference/send-transactional-email)
#### Parameters
| Name | Type | Required | Notes |
| --------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `transactionalId` | string | Yes | The ID of the transactional email to send. |
| `email` | string | Yes | The email address of the recipient. |
| `addToAudience` | boolean | No | If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn’t already exist). |
| `dataVariables` | object | No | An object containing data as defined by the data variables added to the transactional email template.<br />Values can be of type `string` or `number`. |
| `attachments` | object[] | No | A list of attachments objects.<br />**Please note**: Attachments need to be enabled on your account before using them with the API. [Read more](https://loops.so/docs/transactional/attachments) |
| `attachments[].filename` | string | No | The name of the file, shown in email clients. |
| `attachments[].contentType` | string | No | The MIME type of the file. |
| `attachments[].data` | string | No | The base64-encoded content of the file. |
| `headers` | object | No | Additional headers to send with the request. |
#### Examples (Python)
```python
# Minimal
client.send_transactional_email(
transactional_id="clfq6dinn000yl70fgwwyp82l",
email="hello@gmail.com",
data_variables={"loginUrl": "https://myapp.com/login/"},
)
# With Idempotency-Key header
client.send_transactional_email(
transactional_id="clfq6dinn000yl70fgwwyp82l",
email="hello@gmail.com",
data_variables={"loginUrl": "https://myapp.com/login/"},
headers={"Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000"},
)
# With attachments (requires attachments to be enabled on your account)
client.send_transactional_email(
transactional_id="clfq6dinn000yl70fgwwyp82l",
email="hello@gmail.com",
data_variables={"loginUrl": "https://myapp.com/login/"},
attachments=[
{
"filename": "presentation.pdf",
"contentType": "application/pdf",
"data": "JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPD...",
}
],
)
```
#### Response
```json
{
"success": true
}
```
Error handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).
```json
HTTP 400 Bad Request
{
"success": false,
"path": "dataVariables",
"message": "There are required fields for this email. You need to include a 'dataVariables' object with the required fields."
}
```
```json
HTTP 400 Bad Request
{
"success": false,
"error": {
"path": "dataVariables",
"message": "Missing required fields: login_url"
},
"transactionalId": "clfq6dinn000yl70fgwwyp82l"
}
```
---
### getTransactionalEmails()
Get a list of published transactional emails.
[API Reference](https://loops.so/docs/api-reference/list-transactional-emails)
#### Parameters
| Name | Type | Required | Notes |
| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
| `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
#### Example (Python)
```python
client.get_transactional_emails()
client.get_transactional_emails(per_page=15)
```
#### Response
```json
{
"pagination": {
"totalResults": 23,
"returnedResults": 20,
"perPage": 20,
"totalPages": 2,
"nextCursor": "clyo0q4wo01p59fsecyxqsh38",
"nextPage": "https://app.loops.so/api/v1/transactional?cursor=clyo0q4wo01p59fsecyxqsh38&perPage=20"
},
"data": [
{
"id": "clfn0k1yg001imo0fdeqg30i8",
"lastUpdated": "2023-11-06T17:48:07.249Z",
"dataVariables": []
},
{
"id": "cll42l54f20i1la0lfooe3z12",
"lastUpdated": "2025-02-02T02:56:28.845Z",
"dataVariables": [
"confirmationUrl"
]
},
{
"id": "clw6rbuwp01rmeiyndm80155l",
"lastUpdated": "2024-05-14T19:02:52.000Z",
"dataVariables": [
"firstName",
"lastName",
"inviteLink"
]
},
...
]
}
```
---
## Version history
- `v5.0.1` (May 13, 2025) - Added a `headers` parameter for [`sendEvent()`](#sendevent) and [`sendTransactionalEmail()`](#sendtransactionalemail), enabling support for the `Idempotency-Key` header.
- `v5.0.0` (Apr 29, 2025)
- Types are now exported so you can use them in your application.
- `ValidationError` is now thrown when parameters are not added correctly.
- `Error` is now returned if the API key is missing.
- Added tests.
- `v4.1.0` (Feb 27, 2025) - Support for new [List transactional emails](#gettransactionalemails) endpoint.
- `v4.0.0` (Jan 16, 2025)
- Added `APIError` to more easily understand API errors. [See usage example](#usage).
- Added support for two new contact property endpoints: [List contact properties](#listcontactproperties) and [Create contact property](#createcontactproperty).
- Deprecated and removed the `getCustomFields()` method (you can now use [`listContactProperties()`](#listcontactproperties) instead).
- `v3.4.1` (Dec 18, 2024) - Support for a new `description` attribute in [`getMailingLists()`](#getmailinglists).
- `v3.4.0` (Oct 29, 2024) - Added rate limit handling with [`RateLimitExceededError`](#handling-rate-limits).
- `v3.3.0` (Sep 9, 2024) - Added [`testApiKey()`](#testapikey) method.
- `v3.2.0` (Aug 23, 2024) - Added support for a new `mailingLists` attribute in [`findContact()`](#findcontact).
- `v3.1.1` (Aug 16, 2024) - Support for a new `isPublic` attribute in [`getMailingLists()`](#getmailinglists).
- `v3.1.0` (Aug 12, 2024) - The SDK now accepts `null` as a value for contact properties in `createContact()`, `updateContact()` and `sendEvent()`, which allows you to reset/empty properties.
- `v3.0.0` (Jul 2, 2024) - [`sendTransactionalEmail()`](#sendtransactionalemail) now accepts an object instead of separate parameters (breaking change).
- `v2.2.0` (Jul 2, 2024) - Deprecated. Added new `addToAudience` option to [`sendTransactionalEmail()`](#sendtransactionalemail).
- `v2.1.1` (Jun 20, 2024) - Added support for mailing lists in [`createContact()`](#createcontact), [`updateContact()`](#updatecontact) and [`sendEvent()`](#sendevent).
- `v2.1.0` (Jun 19, 2024) - Added support for new [List mailing lists](#getmailinglists) endpoint.
- `v2.0.0` (Apr 19, 2024)
- Added `userId` as a parameter to [`findContact()`](#findcontact). This includes a breaking change for the `findContact()` parameters.
- `userId` values must now be strings (could have also been numbers previously).
- `v1.0.1` (Apr 1, 2024) - Fixed types for `sendEvent()`.
- `v1.0.0` (Mar 28, 2024) - Fix for ESM types. Switched to named export.
- `v0.4.0` (Mar 22, 2024) - Support for new `eventProperties` in [`sendEvent()`](#sendevent). This includes a breaking change for the `sendEvent()` parameters.
- `v0.3.0` (Feb 22, 2024) - Updated minimum Node version to 18.0.0.
- `v0.2.1` (Feb 6, 2024) - Fix for ESM imports.
- `v0.2.0` (Feb 1, 2024) - CommonJS support.
- `v0.1.5` (Jan 25, 2024) - `getCustomFields()` now returns `type` values for each contact property.
- `v0.1.4` (Jan 25, 2024) - Added support for `userId` in [`sendEvent()`](#sendevent) request. Added missing error response type for `sendEvent()` requests.
- `v0.1.3` (Dec 8, 2023) - Added support for transactional attachments.
- `v0.1.2` (Dec 6, 2023) - Improved transactional error types.
- `v0.1.1` (Nov 1, 2023) - Initial release.
---
## Development
This project uses `uv`.
```bash
uv sync
uv run pytest
uv run ruff check
uv run mypy .
```
## Publishing
Use GitHub Actions with PyPI Trusted Publishing.
Workflow:
- Tag a release locally: `git tag v0.2.0 && git push --tags`.
- The workflow `.github/workflows/release.yml` builds and publishes to PyPI on tag push using [PyPA’s official action](https://github.com/marketplace/actions/pypi-publish).
- To publish to TestPyPI manually, trigger the workflow (Run workflow) and choose `testpypi`.
### Installing from TestPyPI
If you published to TestPyPI, install with an explicit index URL:
```bash
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple \
loops-python-unofficial
```
### Releasing
1. Update version in `pyproject.toml`.
2. Create a tag: `git tag vX.Y.Z && git push --tags`.
3. Workflow builds with `uv build` and publishes via trusted publishing.
4. Alternatively dispatch the workflow and select `testpypi` to publish to TestPyPI.
---
## Contributing
Bug reports and pull requests are welcome. Please read our [Contributing Guidelines](CONTRIBUTING.md).
Raw data
{
"_id": null,
"home_page": null,
"name": "loops-python-unofficial",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "email, loops, marketing, sdk, transactional",
"author": null,
"author_email": "VersionLens <support@versionlens.com>",
"download_url": "https://files.pythonhosted.org/packages/c5/eb/ef91b7c6d3e91bd4d48af912c853c7da795125ba332da435510f0864e742/loops_python_unofficial-0.1.0.tar.gz",
"platform": null,
"description": "# Unofficial Loops Python SDK\n\n## Introduction\n\nPython SDK for [Loops](https://loops.so), with both sync and async clients. Mirrors the public JS SDK where it makes sense.\n\n## Installation\n\n```bash\npip install loops-python-unofficial\n```\n\nYou will need a Loops API key from your account settings.\n\n## Quick start\n\n### Sync\n\n```python\nfrom loops_unofficial import LoopsClient, APIError, RateLimitExceededError\n\nclient = LoopsClient(\"<YOUR_API_KEY>\")\n\ntry:\n resp = client.create_contact(\"email@provider.com\")\nexcept RateLimitExceededError as e:\n print(f\"Rate limit exceeded ({e.limit}/s), remaining={e.remaining}\")\nexcept APIError as e:\n print(e.status_code, e.json)\n```\n\n## Handling rate limits\n\nIf you import `RateLimitExceededError` you can check for rate limit issues with your requests.\n\nYou can access details about the rate limits from the `limit` and `remaining` attributes.\n\n### Async\n\n```python\nimport asyncio\nfrom loops_unofficial import AsyncLoopsClient\n\nasync def main():\n client = AsyncLoopsClient(\"<YOUR_API_KEY>\")\n try:\n resp = await client.create_contact(\"email@provider.com\")\n finally:\n await client.aclose()\n\nasyncio.run(main())\n```\n\n## Default contact properties\n\nEach contact in Loops has a set of default properties. These will always be returned in API results.\n\n- `id`\n- `email`\n- `firstName`\n- `lastName`\n- `source`\n- `subscribed`\n- `userGroup`\n- `userId`\n\n## Custom contact properties\n\nYou can use custom contact properties in API calls. Please make sure to [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.\n\n## Methods\n\n- test_api_key()\n- create_contact(email, properties=None, mailing_lists=None)\n- update_contact(email, properties, mailing_lists=None)\n- find_contact(email=None, user_id=None)\n- delete_contact(email=None, user_id=None)\n- create*contact_property(name, type*)\n- get*contact_properties(list*=None)\n- get_mailing_lists()\n- send_event(email=None, user_id=None, event_name=..., contact_properties=None, event_properties=None, mailing_lists=None, headers=None)\n- send_transactional_email(transactional_id, email, add_to_audience=None, data_variables=None, attachments=None, headers=None)\n- get_transactional_emails(per_page=None, cursor=None)\n\n---\n\n### testApiKey()\n\nTest that an API key is valid.\n\n[API Reference](https://loops.so/docs/api-reference/api-key)\n\n#### Parameters\n\nNone\n\n#### Example (Python)\n\n```python\nfrom loops_unofficial import LoopsClient\n\nclient = LoopsClient(\"<API_KEY>\")\nresp = client.test_api_key()\n```\n\n#### Response\n\n```json\n{\n \"success\": true,\n \"teamName\": \"My team\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 401 Unauthorized\n{\n \"error\": \"Invalid API key\"\n}\n```\n\n---\n\n### createContact()\n\nCreate a new contact.\n\n[API Reference](https://loops.so/docs/api-reference/create-contact)\n\n#### Parameters\n\n| Name | Type | Required | Notes |\n| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `email` | string | Yes | If a contact already exists with this email address, an error response will be returned. |\n| `properties` | object | No | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)). |\n| `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. |\n\n#### Examples (Python)\n\n```python\nfrom loops_unofficial import LoopsClient\n\nclient = LoopsClient(\"<API_KEY>\")\n\n# Minimal\nresp = client.create_contact(\"hello@gmail.com\")\n\n# With properties and mailing lists\ncontact_properties = {\"firstName\": \"Bob\", \"favoriteColor\": \"Red\"}\nmailing_lists = {\"cm06f5v0e45nf0ml5754o9cix\": True, \"cm16k73gq014h0mmj5b6jdi9r\": False}\nresp = client.create_contact(\"hello@gmail.com\", contact_properties, mailing_lists)\n```\n\n#### Response\n\n```json\n{\n \"success\": true,\n \"id\": \"id_of_contact\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n \"success\": false,\n \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### updateContact()\n\nUpdate a contact.\n\nNote: To update a contact's email address, the contact requires a `userId` value. Then you can make a request with their `userId` and an updated email address.\n\n[API Reference](https://loops.so/docs/api-reference/update-contact)\n\n#### Parameters\n\n| Name | Type | Required | Notes |\n| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `email` | string | Yes | The email address of the contact to update. If there is no contact with this email address, a new contact will be created using the email and properties in this request. |\n| `properties` | object | No | An object containing default and any custom properties for your contact.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)). |\n| `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. |\n\n#### Example (Python)\n\n```python\nclient.update_contact(\"hello@gmail.com\", {\"firstName\": \"Bob\", \"favoriteColor\": \"Blue\"})\n\n# Updating a contact's email address using userId\nclient.update_contact(\"newemail@gmail.com\", {\"userId\": \"1234\"})\n```\n\n#### Response\n\n```json\n{\n \"success\": true,\n \"id\": \"id_of_contact\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n \"success\": false,\n \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### findContact()\n\nFind a contact.\n\n[API Reference](https://loops.so/docs/api-reference/find-contact)\n\n#### Parameters\n\nYou must use one parameter in the request.\n\n| Name | Type | Required | Notes |\n| -------- | ------ | -------- | ----- |\n| `email` | string | No | |\n| `userId` | string | No | |\n\n#### Examples (Python)\n\n```python\nclient.find_contact(email=\"hello@gmail.com\")\nclient.find_contact(user_id=\"12345\")\n```\n\n#### Response\n\nThis method will return a list containing a single contact object, which will include all default properties and any custom properties.\n\nIf no contact is found, an empty list will be returned.\n\n```json\n[\n {\n \"id\": \"cll6b3i8901a9jx0oyktl2m4u\",\n \"email\": \"hello@gmail.com\",\n \"firstName\": \"Bob\",\n \"lastName\": null,\n \"source\": \"API\",\n \"subscribed\": true,\n \"userGroup\": \"\",\n \"userId\": \"12345\",\n \"mailingLists\": {\n \"cm06f5v0e45nf0ml5754o9cix\": true\n },\n \"favoriteColor\": \"Blue\" /* Custom property */\n }\n]\n```\n\n---\n\n### deleteContact()\n\nDelete a contact, either by email address or `userId`.\n\n[API Reference](https://loops.so/docs/api-reference/delete-contact)\n\n#### Parameters\n\nYou must use one parameter in the request.\n\n| Name | Type | Required | Notes |\n| -------- | ------ | -------- | ----- |\n| `email` | string | No | |\n| `userId` | string | No | |\n\n#### Example (Python)\n\n```python\nclient.delete_contact(email=\"hello@gmail.com\")\nclient.delete_contact(user_id=\"12345\")\n```\n\n#### Response\n\n```json\n{\n \"success\": true,\n \"message\": \"Contact deleted.\"\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n \"success\": false,\n \"message\": \"An error message here.\"\n}\n```\n\n```json\nHTTP 404 Not Found\n{\n \"success\": false,\n \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### createContactProperty()\n\nCreate a new contact property.\n\n[API Reference](https://loops.so/docs/api-reference/create-contact-property)\n\n#### Parameters\n\n| Name | Type | Required | Notes |\n| ------ | ------ | -------- | -------------------------------------------------------------------------------------- |\n| `name` | string | Yes | The name of the property. Should be in camelCase, like `planName` or `favouriteColor`. |\n| `type` | string | Yes | The property's value type.<br />Can be one of `string`, `number`, `boolean` or `date`. |\n\n#### Examples (Python)\n\n```python\nclient.create_contact_property(\"planName\", \"string\")\n```\n\n#### Response\n\n```json\n{\n \"success\": true\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n \"success\": false,\n \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### getContactProperties()\n\nGet a list of your account's contact properties.\n\n[API Reference](https://loops.so/docs/api-reference/list-contact-properties)\n\n#### Parameters\n\n| Name | Type | Required | Notes |\n| ------ | ------ | -------- | --------------------------------------------------------------- |\n| `list` | string | No | Use \"custom\" to retrieve only your account's custom properties. |\n\n#### Example (Python)\n\n```python\nclient.get_contact_properties()\nclient.get_contact_properties(\"custom\")\n```\n\n#### Response\n\nThis method will return a list of contact property objects containing `key`, `label` and `type` attributes.\n\n```json\n[\n {\n \"key\": \"firstName\",\n \"label\": \"First Name\",\n \"type\": \"string\"\n },\n {\n \"key\": \"lastName\",\n \"label\": \"Last Name\",\n \"type\": \"string\"\n },\n {\n \"key\": \"email\",\n \"label\": \"Email\",\n \"type\": \"string\"\n },\n {\n \"key\": \"notes\",\n \"label\": \"Notes\",\n \"type\": \"string\"\n },\n {\n \"key\": \"source\",\n \"label\": \"Source\",\n \"type\": \"string\"\n },\n {\n \"key\": \"userGroup\",\n \"label\": \"User Group\",\n \"type\": \"string\"\n },\n {\n \"key\": \"userId\",\n \"label\": \"User Id\",\n \"type\": \"string\"\n },\n {\n \"key\": \"subscribed\",\n \"label\": \"Subscribed\",\n \"type\": \"boolean\"\n },\n {\n \"key\": \"createdAt\",\n \"label\": \"Created At\",\n \"type\": \"date\"\n },\n {\n \"key\": \"favoriteColor\",\n \"label\": \"Favorite Color\",\n \"type\": \"string\"\n },\n {\n \"key\": \"plan\",\n \"label\": \"Plan\",\n \"type\": \"string\"\n }\n]\n```\n\n---\n\n### getMailingLists()\n\nGet a list of your account's mailing lists. [Read more about mailing lists](https://loops.so/docs/contacts/mailing-lists)\n\n[API Reference](https://loops.so/docs/api-reference/list-mailing-lists)\n\n#### Parameters\n\nNone\n\n#### Example (Python)\n\n```python\nclient.get_mailing_lists()\n```\n\n#### Response\n\nThis method will return a list of mailing list objects containing `id`, `name`, `description` and `isPublic` attributes.\n\nIf your account has no mailing lists, an empty list will be returned.\n\n```json\n[\n {\n \"id\": \"cm06f5v0e45nf0ml5754o9cix\",\n \"name\": \"Main list\",\n \"description\": \"All customers.\",\n \"isPublic\": true\n },\n {\n \"id\": \"cm16k73gq014h0mmj5b6jdi9r\",\n \"name\": \"Investors\",\n \"description\": null,\n \"isPublic\": false\n }\n]\n```\n\n---\n\n### sendEvent()\n\nSend an event to trigger an email in Loops. [Read more about events](https://loops.so/docs/events)\n\n[API Reference](https://loops.so/docs/api-reference/send-event)\n\n#### Parameters\n\n| Name | Type | Required | Notes |\n| ------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `email` | string | No | The contact's email address. Required if `userId` is not present. |\n| `userId` | string | No | The contact's unique user ID. If you use `userId` without `email`, this value must have already been added to your contact in Loops. Required if `email` is not present. |\n| `eventName` | string | Yes | |\n| `contactProperties` | object | No | An object containing contact properties, which will be updated or added to the contact when the event is received.<br />Please [add custom properties](https://loops.so/docs/contacts/properties#custom-contact-properties) in your Loops account before using them with the SDK.<br />Values can be of type `string`, `number`, `null` (to reset a value), `boolean` or `date` ([see allowed date formats](https://loops.so/docs/contacts/properties#dates)). |\n| `eventProperties` | object | No | An object containing event properties, which will be made available in emails that are triggered by this event.<br />Values can be of type `string`, `number`, `boolean` or `date` ([see allowed date formats](https://loops.so/docs/events/properties#important-information-about-event-properties)). |\n| `mailingLists` | object | No | An object of mailing list IDs and boolean subscription statuses. |\n| `headers` | object | No | Additional headers to send with the request. |\n\n#### Examples (Python)\n\n```python\n# Minimal\nclient.send_event(email=\"hello@gmail.com\", event_name=\"signup\")\n\n# With event properties and mailing lists\nclient.send_event(\n email=\"hello@gmail.com\",\n event_name=\"signup\",\n event_properties={\"username\": \"user1234\", \"signupDate\": \"2024-03-21T10:09:23Z\"},\n mailing_lists={\"cm06f5v0e45nf0ml5754o9cix\": True, \"cm16k73gq014h0mmj5b6jdi9r\": False},\n)\n\n# With both email and userId and contact properties\nclient.send_event(\n user_id=\"1234567890\",\n email=\"hello@gmail.com\",\n event_name=\"signup\",\n contact_properties={\"firstName\": \"Bob\", \"plan\": \"pro\"},\n)\n\n# With Idempotency-Key header\nclient.send_event(\n email=\"hello@gmail.com\",\n event_name=\"signup\",\n headers={\"Idempotency-Key\": \"550e8400-e29b-41d4-a716-446655440000\"},\n)\n```\n\n#### Response\n\n```json\n{\n \"success\": true\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n \"success\": false,\n \"message\": \"An error message here.\"\n}\n```\n\n---\n\n### sendTransactionalEmail()\n\nSend a transactional email to a contact. [Learn about sending transactional email](https://loops.so/docs/transactional/guide)\n\n[API Reference](https://loops.so/docs/api-reference/send-transactional-email)\n\n#### Parameters\n\n| Name | Type | Required | Notes |\n| --------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `transactionalId` | string | Yes | The ID of the transactional email to send. |\n| `email` | string | Yes | The email address of the recipient. |\n| `addToAudience` | boolean | No | If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn\u2019t already exist). |\n| `dataVariables` | object | No | An object containing data as defined by the data variables added to the transactional email template.<br />Values can be of type `string` or `number`. |\n| `attachments` | object[] | No | A list of attachments objects.<br />**Please note**: Attachments need to be enabled on your account before using them with the API. [Read more](https://loops.so/docs/transactional/attachments) |\n| `attachments[].filename` | string | No | The name of the file, shown in email clients. |\n| `attachments[].contentType` | string | No | The MIME type of the file. |\n| `attachments[].data` | string | No | The base64-encoded content of the file. |\n| `headers` | object | No | Additional headers to send with the request. |\n\n#### Examples (Python)\n\n```python\n# Minimal\nclient.send_transactional_email(\n transactional_id=\"clfq6dinn000yl70fgwwyp82l\",\n email=\"hello@gmail.com\",\n data_variables={\"loginUrl\": \"https://myapp.com/login/\"},\n)\n\n# With Idempotency-Key header\nclient.send_transactional_email(\n transactional_id=\"clfq6dinn000yl70fgwwyp82l\",\n email=\"hello@gmail.com\",\n data_variables={\"loginUrl\": \"https://myapp.com/login/\"},\n headers={\"Idempotency-Key\": \"550e8400-e29b-41d4-a716-446655440000\"},\n)\n\n# With attachments (requires attachments to be enabled on your account)\nclient.send_transactional_email(\n transactional_id=\"clfq6dinn000yl70fgwwyp82l\",\n email=\"hello@gmail.com\",\n data_variables={\"loginUrl\": \"https://myapp.com/login/\"},\n attachments=[\n {\n \"filename\": \"presentation.pdf\",\n \"contentType\": \"application/pdf\",\n \"data\": \"JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPD...\",\n }\n ],\n)\n```\n\n#### Response\n\n```json\n{\n \"success\": true\n}\n```\n\nError handling is done through the `APIError` class, which provides `statusCode` and `json` properties containing the API's error response details. For implementation examples, see the [Usage section](#usage).\n\n```json\nHTTP 400 Bad Request\n{\n \"success\": false,\n \"path\": \"dataVariables\",\n \"message\": \"There are required fields for this email. You need to include a 'dataVariables' object with the required fields.\"\n}\n```\n\n```json\nHTTP 400 Bad Request\n{\n \"success\": false,\n \"error\": {\n \"path\": \"dataVariables\",\n \"message\": \"Missing required fields: login_url\"\n },\n \"transactionalId\": \"clfq6dinn000yl70fgwwyp82l\"\n}\n```\n\n---\n\n### getTransactionalEmails()\n\nGet a list of published transactional emails.\n\n[API Reference](https://loops.so/docs/api-reference/list-transactional-emails)\n\n#### Parameters\n\n| Name | Type | Required | Notes |\n| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |\n| `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |\n| `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |\n\n#### Example (Python)\n\n```python\nclient.get_transactional_emails()\nclient.get_transactional_emails(per_page=15)\n```\n\n#### Response\n\n```json\n{\n \"pagination\": {\n \"totalResults\": 23,\n \"returnedResults\": 20,\n \"perPage\": 20,\n \"totalPages\": 2,\n \"nextCursor\": \"clyo0q4wo01p59fsecyxqsh38\",\n \"nextPage\": \"https://app.loops.so/api/v1/transactional?cursor=clyo0q4wo01p59fsecyxqsh38&perPage=20\"\n },\n \"data\": [\n {\n \"id\": \"clfn0k1yg001imo0fdeqg30i8\",\n \"lastUpdated\": \"2023-11-06T17:48:07.249Z\",\n \"dataVariables\": []\n },\n {\n \"id\": \"cll42l54f20i1la0lfooe3z12\",\n \"lastUpdated\": \"2025-02-02T02:56:28.845Z\",\n \"dataVariables\": [\n \"confirmationUrl\"\n ]\n },\n {\n \"id\": \"clw6rbuwp01rmeiyndm80155l\",\n \"lastUpdated\": \"2024-05-14T19:02:52.000Z\",\n \"dataVariables\": [\n \"firstName\",\n \"lastName\",\n \"inviteLink\"\n ]\n },\n ...\n ]\n}\n```\n\n---\n\n## Version history\n\n- `v5.0.1` (May 13, 2025) - Added a `headers` parameter for [`sendEvent()`](#sendevent) and [`sendTransactionalEmail()`](#sendtransactionalemail), enabling support for the `Idempotency-Key` header.\n- `v5.0.0` (Apr 29, 2025)\n - Types are now exported so you can use them in your application.\n - `ValidationError` is now thrown when parameters are not added correctly.\n - `Error` is now returned if the API key is missing.\n - Added tests.\n- `v4.1.0` (Feb 27, 2025) - Support for new [List transactional emails](#gettransactionalemails) endpoint.\n- `v4.0.0` (Jan 16, 2025)\n - Added `APIError` to more easily understand API errors. [See usage example](#usage).\n - Added support for two new contact property endpoints: [List contact properties](#listcontactproperties) and [Create contact property](#createcontactproperty).\n - Deprecated and removed the `getCustomFields()` method (you can now use [`listContactProperties()`](#listcontactproperties) instead).\n- `v3.4.1` (Dec 18, 2024) - Support for a new `description` attribute in [`getMailingLists()`](#getmailinglists).\n- `v3.4.0` (Oct 29, 2024) - Added rate limit handling with [`RateLimitExceededError`](#handling-rate-limits).\n- `v3.3.0` (Sep 9, 2024) - Added [`testApiKey()`](#testapikey) method.\n- `v3.2.0` (Aug 23, 2024) - Added support for a new `mailingLists` attribute in [`findContact()`](#findcontact).\n- `v3.1.1` (Aug 16, 2024) - Support for a new `isPublic` attribute in [`getMailingLists()`](#getmailinglists).\n- `v3.1.0` (Aug 12, 2024) - The SDK now accepts `null` as a value for contact properties in `createContact()`, `updateContact()` and `sendEvent()`, which allows you to reset/empty properties.\n- `v3.0.0` (Jul 2, 2024) - [`sendTransactionalEmail()`](#sendtransactionalemail) now accepts an object instead of separate parameters (breaking change).\n- `v2.2.0` (Jul 2, 2024) - Deprecated. Added new `addToAudience` option to [`sendTransactionalEmail()`](#sendtransactionalemail).\n- `v2.1.1` (Jun 20, 2024) - Added support for mailing lists in [`createContact()`](#createcontact), [`updateContact()`](#updatecontact) and [`sendEvent()`](#sendevent).\n- `v2.1.0` (Jun 19, 2024) - Added support for new [List mailing lists](#getmailinglists) endpoint.\n- `v2.0.0` (Apr 19, 2024)\n - Added `userId` as a parameter to [`findContact()`](#findcontact). This includes a breaking change for the `findContact()` parameters.\n - `userId` values must now be strings (could have also been numbers previously).\n- `v1.0.1` (Apr 1, 2024) - Fixed types for `sendEvent()`.\n- `v1.0.0` (Mar 28, 2024) - Fix for ESM types. Switched to named export.\n- `v0.4.0` (Mar 22, 2024) - Support for new `eventProperties` in [`sendEvent()`](#sendevent). This includes a breaking change for the `sendEvent()` parameters.\n- `v0.3.0` (Feb 22, 2024) - Updated minimum Node version to 18.0.0.\n- `v0.2.1` (Feb 6, 2024) - Fix for ESM imports.\n- `v0.2.0` (Feb 1, 2024) - CommonJS support.\n- `v0.1.5` (Jan 25, 2024) - `getCustomFields()` now returns `type` values for each contact property.\n- `v0.1.4` (Jan 25, 2024) - Added support for `userId` in [`sendEvent()`](#sendevent) request. Added missing error response type for `sendEvent()` requests.\n- `v0.1.3` (Dec 8, 2023) - Added support for transactional attachments.\n- `v0.1.2` (Dec 6, 2023) - Improved transactional error types.\n- `v0.1.1` (Nov 1, 2023) - Initial release.\n\n---\n\n## Development\n\nThis project uses `uv`.\n\n```bash\nuv sync\nuv run pytest\nuv run ruff check\nuv run mypy .\n```\n\n## Publishing\n\nUse GitHub Actions with PyPI Trusted Publishing.\n\nWorkflow:\n\n- Tag a release locally: `git tag v0.2.0 && git push --tags`.\n- The workflow `.github/workflows/release.yml` builds and publishes to PyPI on tag push using [PyPA\u2019s official action](https://github.com/marketplace/actions/pypi-publish).\n- To publish to TestPyPI manually, trigger the workflow (Run workflow) and choose `testpypi`.\n\n### Installing from TestPyPI\n\nIf you published to TestPyPI, install with an explicit index URL:\n\n```bash\npip install --index-url https://test.pypi.org/simple/ \\\n --extra-index-url https://pypi.org/simple \\\n loops-python-unofficial\n```\n\n### Releasing\n\n1. Update version in `pyproject.toml`.\n2. Create a tag: `git tag vX.Y.Z && git push --tags`.\n3. Workflow builds with `uv build` and publishes via trusted publishing.\n4. Alternatively dispatch the workflow and select `testpypi` to publish to TestPyPI.\n\n---\n\n## Contributing\n\nBug reports and pull requests are welcome. Please read our [Contributing Guidelines](CONTRIBUTING.md).\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Unofficial Python SDK for Loops (loops.so) with sync and async clients",
"version": "0.1.0",
"project_urls": {
"Documentation": "https://loops.so/docs",
"Homepage": "https://loops.so",
"Repository": "https://github.com/VersionLens/loops-python-unofficial"
},
"split_keywords": [
"email",
" loops",
" marketing",
" sdk",
" transactional"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "6e860822f91202abb93848b143e9888b75603e6e2add5efcba88bb7a71a1eda0",
"md5": "8f8093025b04868ba1918c9dcf1026d0",
"sha256": "f128f03849eb592dacaa4265c83aff3b3cd47fb339aa4498c8dd436e57497269"
},
"downloads": -1,
"filename": "loops_python_unofficial-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "8f8093025b04868ba1918c9dcf1026d0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 13495,
"upload_time": "2025-08-09T14:44:18",
"upload_time_iso_8601": "2025-08-09T14:44:18.847569Z",
"url": "https://files.pythonhosted.org/packages/6e/86/0822f91202abb93848b143e9888b75603e6e2add5efcba88bb7a71a1eda0/loops_python_unofficial-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c5ebef91b7c6d3e91bd4d48af912c853c7da795125ba332da435510f0864e742",
"md5": "58f320162c1b5e1ba2d5a4dcabd6ac46",
"sha256": "7231ce0debd7ff9d4dd51ffce7273b49e839cde9ce9611ee9a76adcb23820bc1"
},
"downloads": -1,
"filename": "loops_python_unofficial-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "58f320162c1b5e1ba2d5a4dcabd6ac46",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 35374,
"upload_time": "2025-08-09T14:44:20",
"upload_time_iso_8601": "2025-08-09T14:44:20.430906Z",
"url": "https://files.pythonhosted.org/packages/c5/eb/ef91b7c6d3e91bd4d48af912c853c7da795125ba332da435510f0864e742/loops_python_unofficial-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-09 14:44:20",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "VersionLens",
"github_project": "loops-python-unofficial",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "loops-python-unofficial"
}