diff --git a/docs/endpoints/post-identity-buckets.md b/docs/endpoints/post-identity-buckets.md index 866a60f7b..3b7644dd4 100644 --- a/docs/endpoints/post-identity-buckets.md +++ b/docs/endpoints/post-identity-buckets.md @@ -14,6 +14,12 @@ Monitors rotated salt bucke Used by: This endpoint is used mainly by advertisers and data providers. For details, see [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md). +:::important +If you're using the latest version (v3) of `POST /v3/identity/map`, you don't need to use `POST /identity/buckets` at all. You only need to use it if you're using the earlier version (v2) of `POST /v2/identity/map`. + +If you're using the v2 version, we recommend that you upgrade as soon as possible, to take advantage of improvements. For migration guidance, see [Migration from v2 Identity Map](post-identity-map.md#migration-from-v2-identity-map). +::: + ## Request Format `POST '{environment}/v2/identity/buckets'` diff --git a/docs/endpoints/post-identity-map-v2.md b/docs/endpoints/post-identity-map-v2.md new file mode 100644 index 000000000..0478a0b00 --- /dev/null +++ b/docs/endpoints/post-identity-map-v2.md @@ -0,0 +1,207 @@ +--- +title: POST /identity/map (v2) +description: Maps DII to raw UID2s and salt bucket IDs. +hide_table_of_contents: false +sidebar_position: 07 +--- + +import Link from '@docusaurus/Link'; + +# POST /identity/map (v2) + +Maps multiple email addresses, phone numbers, or their respective hashes to their raw UID2s and salt bucket IDs. You can also use this endpoint to check for updates to opt-out information. + +Used by: This endpoint is used mainly by advertisers and data providers. For details, see [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md). + +For details about the UID2 opt-out workflow and how users can opt out, see [User Opt-Out](../getting-started/gs-opt-out.md). + +## Version + +This documentation is for version 2 of this endpoint, which is not the latest version. For the latest version, v3, see [POST /identity/map](post-identity-map.md). + +:::note +If you're using the v2 version, we recommend that you upgrade as soon as possible, to take advantage of improvements. For migration guidance, see [Migration from v2 Identity Map](post-identity-map.md#migration-from-v2-identity-map). +::: + +## Batch Size and Request Parallelization Requirements + +Here's what you need to know: + +- The maximum request size is 1MB. +- To map a large number of email addresses, phone numbers, or their respective hashes, send them in *sequential* batches with a maximum batch size of 5,000 items per batch. +- Unless you are using a Private Operator, do not send batches in parallel. In other words, use a single HTTP connection and send batches of hashed or unhashed directly identifying information (DII) values consecutively, without creating multiple parallel connections. +- Be sure to store mappings of email addresses, phone numbers, or their respective hashes.
Not storing mappings could increase processing time drastically when you have to map millions of email addresses or phone numbers. Recalculating only those mappings that actually need to be updated, however, reduces the total processing time because only about 1/365th of raw UID2s need to be updated daily. See also [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) and [FAQs for Advertisers and Data Providers](../getting-started/gs-faqs.md#faqs-for-advertisers-and-data-providers). + +## Request Format + +`POST '{environment}/v2/identity/map'` + +For authentication details, see [Authentication and Authorization](../getting-started/gs-auth.md). + +:::important +You must encrypt all requests using your secret. For details, and code examples in different programming languages, see [Encrypting Requests and Decrypting Responses](../getting-started/gs-encryption-decryption.md). +::: + +### Path Parameters + +| Path Parameter | Data Type | Attribute | Description | +| :--- | :--- | :--- | :--- | +| `{environment}` | string | Required | Testing (integration) environment: `https://operator-integ.uidapi.com`
Production environment: The best choice depends on where your users are based. For information about how to choose the best URL for your use case, and a full list of valid base URLs, see [Environments](../getting-started/gs-environments.md). | + +:::note +The integration environment and the production environment require different API keys. For information about getting credentials for each environment, see [Getting Your Credentials](../getting-started/gs-credentials.md#getting-your-credentials). +::: + +### Unencrypted JSON Body Parameters + +:::important +You must include only **one** of the following four conditional parameters as a key-value pair in the JSON body of the request when encrypting it. +::: + +| Body Parameter | Data Type | Attribute | Description | +| :--- | :--- | :--- | :--- | +| `email` | string array | Conditionally Required | The list of email addresses to be mapped. | +| `email_hash` | string array | Conditionally Required | The list of [Base64-encoded SHA-256](../getting-started/gs-normalization-encoding.md#email-address-hash-encoding) hashes of [normalized](../getting-started/gs-normalization-encoding.md#email-address-normalization) email addresses to be mapped. | +| `phone` | string array | Conditionally Required | The list of [normalized](../getting-started/gs-normalization-encoding.md#phone-number-normalization) phone numbers to be mapped. | +| `phone_hash` | string array | Conditionally Required | The list of [Base64-encoded SHA-256](../getting-started/gs-normalization-encoding.md#phone-number-hash-encoding) hashes of [normalized](../getting-started/gs-normalization-encoding.md#phone-number-normalization) phone numbers to be mapped. | + +### Request Examples + +The following are unencrypted JSON request body examples for each parameter, one of which you should include in your requests to the `POST /identity/map` endpoint: + +```json +{ + "email":[ + "user@example.com", + "user2@example.com" + ] +} +``` +```json +{ + "email_hash":[ + "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=", + "KzsrnOhCq4tqbGFMsflgS7ig1QLRr0nFJrcrEIlOlbU=" + ] +} +``` +```json +{ + "phone":[ + "+12345678901", + "+441234567890" + ] +} +``` +```json +{ + "phone_hash":[ + "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "Rx8SW4ZyKqbPypXmswDNuq0SPxStFXBTG/yvPns/2NQ=" + ] +} +``` + +Here's an encrypted request example to the `POST /identity/map` endpoint for a phone number: + +```sh +echo '{"phone": ["+12345678901", "+441234567890"]}' | python3 uid2_request.py https://prod.uidapi.com/v2/identity/map [Your-Client-API-Key] [Your-Client-Secret] +``` + +For details, and code examples in different programming languages, see [Encrypting Requests and Decrypting Responses](../getting-started/gs-encryption-decryption.md). + +## Decrypted JSON Response Format + +:::note +The response is encrypted only if the HTTP status code is 200. Otherwise, the response is not encrypted. +::: + +A successful decrypted response returns the raw UID2s and salt bucket IDs for the specified email addresses, phone numbers, or their respective hashes. + +```json +{ + "body":{ + "mapped": [ + { + "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", + "bucket_id": "a30od4mNRd" + }, + { + "identifier": "Rx8SW4ZyKqbPypXmswDNuq0SPxStFXBTG/yvPns/2NQ=", + "advertising_id": "IbW4n6LIvtDj/8fCESlU0QG9K/fH63UdcTkJpAG8fIQ=", + "bucket_id": "ad1ANEmVZ" + } + ] + }, + "status":"success" +} +``` + +If some identifiers are considered invalid, they are included in the response in an "unmapped" list. In this case, the response status is still "success". If all identifiers are mapped, the "unmapped" list is not included in the response. + +```json +{ + "body":{ + "mapped": [ + { + "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", + "bucket_id": "a30od4mNRd" + } + ], + "unmapped": [ + { + "identifier": "some@malformed@email@hash", + "reason": "invalid identifier" + } + ] + }, + "status":"success" +} +``` + +If some identifiers have opted out from the UID2 ecosystem, the opted-out identifiers are moved to the "unmapped" list along with any invalid identifiers found. In this case, the response status is still "success". + +```json +{ + "body":{ + "mapped": [ + { + "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", + "bucket_id": "a30od4mNRd" + } + ], + "unmapped": [ + { + "identifier": "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=", + "reason": "optout" + } + ] + }, + "status":"success" +} +``` + +### Response Body Properties + +The response body includes the properties shown in the following table. + +| Property | Data Type | Description | +| :--- | :--- | :--- | +| `identifier` | string | The email address, phone number, or respective hash specified in the request body. | +| `advertising_id` | string | The corresponding advertising ID (raw UID2). | +| `bucket_id` | string | The ID of the salt bucket used to generate the raw UID2. | + +### Response Status Codes + +The following table lists the `status` property values and their HTTP status code equivalents. + +| Status | HTTP Status Code | Description | +| :--- | :--- | :--- | +| `success` | 200 | The request was successful. The response will be encrypted. | +| `client_error` | 400 | The request had missing or invalid parameters.| +| `unauthorized` | 401 | The request did not include a bearer token, included an invalid bearer token, or included a bearer token unauthorized to perform the requested operation. | + +If the `status` value is anything other than `success`, the `message` field provides additional information about the issue. diff --git a/docs/endpoints/post-identity-map.md b/docs/endpoints/post-identity-map.md index e415df5c7..d292def72 100644 --- a/docs/endpoints/post-identity-map.md +++ b/docs/endpoints/post-identity-map.md @@ -1,21 +1,27 @@ --- title: POST /identity/map -description: Maps DII to raw UID2s and salt bucket IDs. +description: Maps DII to raw UID2s. hide_table_of_contents: false sidebar_position: 08 -displayed_sidebar: docs +displayed_sidebar: docs --- import Link from '@docusaurus/Link'; # POST /identity/map -Maps multiple email addresses, phone numbers, or their respective hashes to their raw UID2s and salt bucket IDs. You can also use this endpoint to check for updates to opt-out information. +Maps multiple email addresses, phone numbers, or their respective hashes to their raw UID2s. You can also use this endpoint to check for updates to opt-out information, check when a raw UID2 can be refreshed, or view the previous UID2 if the current UID2 is less than 90 days old. Used by: This endpoint is used mainly by advertisers and data providers. For details, see [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md). For details about the UID2 opt-out workflow and how users can opt out, see [User Opt-Out](../getting-started/gs-opt-out.md). +## Version + +This documentation is for the latest version of this endpoint, version 3. + +If needed, documentation is also available for the previous version: see [POST /identity/map (v2)](post-identity-map-v2.md). + ## Batch Size and Request Parallelization Requirements Here's what you need to know: @@ -23,11 +29,11 @@ Here's what you need to know: - The maximum request size is 1MB. - To map a large number of email addresses, phone numbers, or their respective hashes, send them in *sequential* batches with a maximum batch size of 5,000 items per batch. - Unless you are using a Private Operator, do not send batches in parallel. In other words, use a single HTTP connection and send batches of hashed or unhashed directly identifying information (DII) values consecutively, without creating multiple parallel connections. -- Be sure to store mappings of email addresses, phone numbers, or their respective hashes.
Not storing mappings could increase processing time drastically when you have to map millions of email addresses or phone numbers. Recalculating only those mappings that actually need to be updated, however, reduces the total processing time because only about 1/365th of raw UID2s need to be updated daily. See also [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) and [FAQs for Advertisers and Data Providers](../getting-started/gs-faqs.md#faqs-for-advertisers-and-data-providers). +- Be sure to store mappings of email addresses, phone numbers, or their respective hashes.
Not storing mappings could increase processing time drastically when you have to map millions of email addresses or phone numbers. Recalculating only those mappings that actually need to be updated, however, reduces the total processing time because only about 1/365th of UID2s need to be updated daily. See also [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) and [FAQs for Advertisers and Data Providers](../getting-started/gs-faqs.md#faqs-for-advertisers-and-data-providers). ## Request Format -`POST '{environment}/v2/identity/map'` +`POST '{environment}/v3/identity/map'` For authentication details, see [Authentication and Authorization](../getting-started/gs-auth.md). @@ -48,46 +54,40 @@ The integration environment and the production environment require different
  • If the current raw UID2 has been rotated in the last 90 days: the previous value.
  • If the current raw UID2 is older than 90 days: `null`.
  • | +| `r` | number | The Unix timestamp (in milliseconds) that indicates when the raw UID2 might be refreshed. The raw UID2 is guaranteed to be valid until this timestamp. | + +For unsuccessfully mapped input values, the mapped object includes the properties shown in the following table. + +| Property | Data Type | Description | +|:---------|:----------|:-----------------------------------------------------------------------------------------------------------------| +| `e` | string | The reason for being unable to map the DII to a raw UID2. One of two possible values: | ### Response Status Codes @@ -194,7 +190,116 @@ The following table lists the `status` property values and their HTTP status cod | Status | HTTP Status Code | Description | | :--- | :--- | :--- | | `success` | 200 | The request was successful. The response will be encrypted. | -| `client_error` | 400 | The request had missing or invalid parameters.| +| `client_error` | 400 | The request had missing or invalid parameters. | | `unauthorized` | 401 | The request did not include a bearer token, included an invalid bearer token, or included a bearer token unauthorized to perform the requested operation. | If the `status` value is anything other than `success`, the `message` field provides additional information about the issue. + +## Migration from v2 Identity Map + +The following sections provide general information and guidance for migrating to version 3 from earlier versions, including: + +- [Version 3 Improvements](#version-3-improvements) +- [Key Differences Between v2 and v3](#key-differences-between-v2-and-v3) +- [Required Changes](#required-changes) +- [Additional Resources](#additional-resources) + +### Version 3 Improvements + +The v3 Identity Map API provides the following improvements over v2: + +- **Simplified Refresh Management**: You can monitor for UID2s reaching `refresh_from` timestamps instead of polling salt buckets for rotation. +- **Previous UID2 Access**: You have access to previous raw UID2s for 90 days after rotation for campaign measurement. +- **Single Endpoint**: You use only one endpoint, `/v3/identity/map`, instead of both `/v2/identity/map` and `/v2/identity/buckets`. +- **Multiple Identity Types in One Request**: You can process both emails and phone numbers in a single request. +- **Improved Performance**: The updated version uses significantly less bandwidth to process the same amount of DII. + +### Key Differences Between v2 and v3 + +The following table shows key differences between the versions. + +| Feature | V2 Implementation | V3 Implementation | +|:-------------------------------|:--------------------------------------------|:-------------------------------------------| +| Endpoints Required | `/v2/identity/map` + `/v2/identity/buckets` | `/v3/identity/map` only | +| Identity Types per Request | Single identity type only | Multiple identity types | +| Refresh Management | Monitor salt bucket rotations via `/identity/buckets` endpoint | Re-map when past `refresh_from` timestamps | +| Previous UID2 Access | Not available | Available for 90 days | + +### Required Changes + +To upgrade from an earlier version to version 3, follow these steps: + +1. [Update Endpoint URL](#1-update-endpoint-url) +2. [Update V3 Response Parsing Logic](#2-update-v3-response-parsing-logic) +3. [Replace Salt Bucket Monitoring with Refresh Timestamp Logic](#3-replace-salt-bucket-monitoring-with-refresh-timestamp-logic) + +#### 1. Update Endpoint URL + +Update any reference to the endpoint URL so that it references the /v3/ implementation, as shown in the following example. + +```python +# Before (v2) +url = '/v2/identity/map' + +# After (v3) +url = '/v3/identity/map' +``` + +#### 2. Update v3 Response Parsing Logic + +Update the logic for parsing the response, as shown in the following example. + +V2 Response Parsing: +```python +# v2: Process mapped/unmapped objects with identifier lookup +for item in response['body']['mapped']: + raw_uid = item['advertising_id'] + bucket_id = item['bucket_id'] + original_identifier = item['identifier'] + # Store mapping using identifier as key + store_mapping(original_identifier, raw_uid, bucket_id) +``` + +V3 Response Parsing: +```python +# v3: Process array-indexed responses +for index, item in enumerate(response['body']['email']): + original_email = request_emails[index] # Use array index to correlate + if 'u' in item: + # Successfully mapped + current_uid = item['u'] + previous_uid = item.get('p') # Available for 90 days after rotation, otherwise None + refresh_from = item['r'] + store_mapping(original_email, current_uid, previous_uid, refresh_from) + elif 'e' in item: + # Handle unmapped with reason + handle_unmapped(original_email, item['e']) +``` + +#### 3. Replace Salt Bucket Monitoring with Refresh Timestamp Logic + +Update your code for salt bucket monitoring, replacing it with code that checks the `refresh_from` timestamp to determine raw UID2s that are due for refresh. + +The following example shows an implementation of the v3 approach for checking refresh timestamps: + +```python +import time + +def is_refresh_needed(mapping): + now = int(time.time() * 1000) # Convert to milliseconds + return now >= mapping['refresh_from'] + +# Check individual mappings for refresh needs +to_remap = [mapping for mapping in mappings if is_refresh_needed(mapping)] +remap_identities(to_remap) +``` + +### Additional Resources +- [SDK for Java](../sdks/sdk-ref-java.md) for Java implementations (see Usage for Advertisers/Data Providers section) + + + + + +For general information about identity mapping, see [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md). diff --git a/docs/endpoints/summary-endpoints.md b/docs/endpoints/summary-endpoints.md index b9ef57d8a..cd865d094 100644 --- a/docs/endpoints/summary-endpoints.md +++ b/docs/endpoints/summary-endpoints.md @@ -22,14 +22,32 @@ The following endpoints are for retrieving and managing UID2 tokens (identity to | [POST /token/validate](post-token-validate.md) | Used for testing, to validate that an advertising token (UID2) matches the specified email address, phone number, or the respective hash. | Required | Required | | [POST /token/refresh](post-token-refresh.md) | Generates a new token for a user for their refresh token from the [POST /token/generate](post-token-generate.md) response. | N/A | Required | -## Identity Maps +## Identity Map The following endpoints are used by advertisers and third-party data providers. Publishers do not need to use these endpoints. +### Latest Identity Map Endpoint (v3) + +In the latest identity map integration, you only need to call one endpoint, `POST /identity/map`. The `POST /identity/buckets` endpoint is not part of the workflow. + +:::important +If you're using the earlier version, we recommend that you upgrade as soon as possible, to take advantage of improvements. +::: + +The latest identity map integration uses the following endpoint: + +| Endpoint | Description | Request Encryption | Response Decryption | +| :--- | :--- | :--- | :--- | +| [POST /identity/map](post-identity-map.md) | Maps raw UID2s, previous raw UID2s, and refresh timestamps for one or more email addresses, phone numbers, or their respective hashes. | Required | Required | + +### Earlier Identity Map Endpoints (v2) + +The following endpoints are part of the earlier identity map integration (version 2). + | Endpoint | Description | Request Encryption | Response Decryption | | :--- | :--- | :--- | :--- | | [POST /identity/buckets](post-identity-buckets.md) | Monitors rotated salt buckets using their last updated timestamp. | Required | Required | -| [POST /identity/map](post-identity-map.md) | Retrieves raw UID2s and salt bucket IDs for one or more email addresses, phone numbers, or their respective hashes. | Required | Required | +| [POST /identity/map (v2)](post-identity-map-v2.md) | Maps raw UID2s and salt bucket IDs for one or more email addresses, phone numbers, or their respective hashes. | Required | Required | ## Opt-Out Status @@ -39,4 +57,4 @@ For details about the UID2 opt-out workflow and how users can opt out, see [User | Endpoint | Description | Request Encryption | Response Decryption | | :--- | :--- | :--- | :--- | -| [POST /optout/status](post-optout-status.md) | Checks the opt-out status of raw UID2s. This endpoint takes a list of raw UID2s as input, and returns the raw UID2s that have opted out, as well as the time that the opt-out took place. | Required | Required | +| [POST /optout/status](post-optout-status.md) | Checks the opt-out status of UID2s. This endpoint takes a list of UID2s as input, and returns the UID2s that have opted out, as well as the time that the opt-out took place. | Required | Required | diff --git a/docs/getting-started/gs-faqs.md b/docs/getting-started/gs-faqs.md index 16b1c67b5..c97c45642 100644 --- a/docs/getting-started/gs-faqs.md +++ b/docs/getting-started/gs-faqs.md @@ -167,36 +167,38 @@ For details, see [Publisher Integration with SSO Providers](/docs/ref-info/ref-i Here are some frequently asked questions for advertisers and data providers using the UID2 framework. -- [How do I know when to refresh the UID2 due to salt bucket rotation?](#how-do-i-know-when-to-refresh-the-uid2-due-to-salt-bucket-rotation) -- [Do refreshed emails get assigned to the same bucket with which they were previously associated?](#do-refreshed-emails-get-assigned-to-the-same-bucket-with-which-they-were-previously-associated) -- [How often should UID2s be refreshed for incremental updates?](#how-often-should-uid2s-be-refreshed-for-incremental-updates) +- [How do I know when to refresh a raw UID2?](#how-do-i-know-when-to-refresh-a-raw-uid2) +- [How often should raw UID2s be refreshed for incremental updates?](#how-often-should-raw-uid2s-be-refreshed-for-incremental-updates) - [How should I generate the SHA-256 of DII for mapping?](#how-should-i-generate-the-sha-256-of-dii-for-mapping) - [Should I store mapping of email addresses, phone numbers, or corresponding hashes to raw UID2s in my own datasets?](#should-i-store-mapping-of-email-addresses-phone-numbers-or-corresponding-hashes-to-raw-uid2s-in-my-own-datasets) - [How should I handle user opt-outs?](#how-should-i-handle-user-opt-outs) - [Does the same DII always result in the same raw UID2?](#does-the-same-dii-always-result-in-the-same-raw-uid2) - [If two operators process the same DII, are the results the same?](#if-two-operators-process-the-same-dii-are-the-results-the-same) +- [How do I know when to refresh the UID2 due to salt bucket rotation?](#how-do-i-know-when-to-refresh-the-uid2-due-to-salt-bucket-rotation) +- [Do refreshed emails get assigned to the same bucket with which they were previously associated?](#do-refreshed-emails-get-assigned-to-the-same-bucket-with-which-they-were-previously-associated) -#### How do I know when to refresh the UID2 due to salt bucket rotation? - -Metadata supplied with the UID2 generation request indicates the salt bucket used for generating the UID2. Salt buckets persist and correspond to the underlying DII used to generate a UID2. Use the [POST /identity/buckets](../endpoints/post-identity-buckets.md) endpoint to return which salt buckets rotated since a given timestamp. The returned rotated salt buckets inform you which UID2s to refresh. +#### How do I know when to refresh a raw UID2? -:::note -We do not make any promises about when the rotation takes place. To stay as up-to-date as possible, we recommend doing the checks once per hour. -::: +The [POST /identity/map](../endpoints/post-identity-map.md) endpoint provides a refresh timestamp in the response (`r` field) that indicates a timestamp, after which each raw UID2 might refresh. Use this timestamp to determine when to regenerate raw UID2s for your stored data. -#### Do refreshed emails get assigned to the same bucket with which they were previously associated? +To determine whether to refresh a raw UID2: -Not necessarily. After you remap emails associated with a particular bucket ID, the emails might be assigned to a different bucket ID. To check the bucket ID, see [Generate Raw UID2s from DII](../guides/integration-advertiser-dataprovider-overview.md#1-generate-raw-uid2s-from-dii) and save the returned raw UID2 and bucket ID again. +1. Compare the current time with the refresh timestamp you stored from the [POST /identity/map](../endpoints/post-identity-map.md) response. +2. If the current time is greater than or equal to the refresh timestamp, regenerate the raw UID2 by calling the identity map endpoint again with the same DII. -:::info -When mapping and remapping emails, do not make any assumptions about the number of buckets, their rotation dates, or the specific bucket that an email gets assigned to. +:::note +We recommend checking for refresh opportunities daily. It is guaranteed that the raw UID2 won't refresh before the indicated timestamp. At some point on or after that time, the raw UID2 is refreshed. ::: -#### How often should UID2s be refreshed for incremental updates? +#### How often should raw UID2s be refreshed for incremental updates? The recommended cadence for updating audiences is daily. -Even though each salt bucket is updated roughly once a year, individual bucket updates are spread over the year. This means that about 1/365th of all buckets are rotated daily. If fidelity is critical, consider calling the [POST /identity/buckets](../endpoints/post-identity-buckets.md) endpoint more frequently; for example, hourly. +A raw UID2 for a specific user changes roughly once per year. The latest version of the [POST /identity/map](../endpoints/post-identity-map.md) endpoint provides refresh timestamps that indicate a point after which each raw UID2 might refresh. We recommend checking these timestamps daily to ensure your raw UID2s remain current and valid for audience targeting. + +For implementations that reference earlier versions of this endpoint (see [POST /identity/map v2](../endpoints/post-identity-map-v2.md)): + +Even though each salt bucket is updated roughly once a year, individual bucket updates are spread over the year. This means that about 1/365th of all buckets are rotated daily. If fidelity is critical, consider calling the [POST /identity/buckets](../endpoints/post-identity-buckets.md) endpoint more frequently; for example, hourly. #### How should I generate the SHA-256 of DII for mapping? @@ -204,7 +206,7 @@ The system should follow the [email normalization rules](gs-normalization-encodi #### Should I store mapping of email addresses, phone numbers, or corresponding hashes to raw UID2s in my own datasets? -Yes. Not storing mappings may increase processing time drastically when you have to map millions of email addresses or phone numbers. Recalculating only those mappings that actually need to be updated, however, reduces the total processing time because only about 1/365th of UID2s need to be updated daily. +Yes. Not storing mappings might increase processing time drastically when you have to map millions of email addresses or phone numbers. Recalculating only those mappings that actually need to be updated, however, reduces the total processing time because only about 1/365th of raw UID2s need to be updated daily. :::important Unless you are using a Private Operator, you must map email addresses, phone numbers, or hashes consecutively, using a single HTTP connection, with a maximum batch size of 5,000 items per batch. In other words, do your mapping without creating multiple parallel connections. @@ -212,7 +214,7 @@ Unless you are using a salt value that's used in generating the raw UID2. The salt values are rotated roughly once per year (for details, see [How often should UID2s be refreshed for incremental updates?](#how-often-should-uid2s-be-refreshed-for-incremental-updates)). If the salt value changes between one request and another, those two requests result in two different raw UID2, even when the DII is the same. +However, there is a variable factor that's used in generating the raw UID2. The underlying values are refreshed roughly once per year (for details, see [How often should raw UID2s be refreshed for incremental updates?](#how-often-should-raw-uid2s-be-refreshed-for-incremental-updates)). If these values change between one request and another, those two requests result in two different raw UID2, even when the DII is the same. -For more information, see [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](../guides/integration-advertiser-dataprovider-overview.md#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s) in the *Advertiser/Data Provider Integration Guide*. +For more information, see [Monitor for Raw UID2 Refresh](../guides/integration-advertiser-dataprovider-overview.md#5-monitor-for-raw-uid2-refresh) in the *Advertiser/Data Provider Integration Guide*. #### If two operators process the same DII, are the results the same? @@ -232,10 +234,26 @@ Yes, if the request is for a r The result is the same, regardless of the Operator and whether it's a Private Operator or a Public Operator. -The timing is important only because of salt bucket rotation. If the salt value changes between one request and another, the result is a different raw UID2. +The timing is important only because of refresh cycles. If the underlying values change between one request and another, the result is a different raw UID2. However, if a publisher sends DII in a request for a UID2 token, via the [POST /token/generate](../endpoints/post-token-generate.md) or [POST /token/refresh](../endpoints/post-token-refresh.md) endpoint or via an SDK, the resulting UID2 token contains the same encrypted raw UID2, but the token itself is always unique. +#### How do I know when to refresh the UID2 due to salt bucket rotation? + +Metadata supplied with the UID2 generation request indicates the salt bucket used for generating the UID2. Salt buckets persist and correspond to the underlying DII used to generate a UID2. Use the [POST /identity/buckets](../endpoints/post-identity-buckets.md) endpoint to return which salt buckets rotated since a given timestamp. The returned rotated salt buckets inform you which UID2s to refresh. + +:::note +We do not make any promises about when the rotation takes place. To stay as up-to-date as possible, we recommend doing the checks once per hour. +::: + +#### Do refreshed emails get assigned to the same bucket with which they were previously associated? + +Not necessarily. After you remap emails associated with a particular bucket ID, the emails might be assigned to a different bucket ID. To check the bucket ID, see [Generate Raw UID2s from DII](../guides/integration-advertiser-dataprovider-overview.md#1-generate-raw-uid2s-from-dii) and save the returned raw UID2 and bucket ID again. + +:::info +When mapping and remapping emails, do not make any assumptions about the number of buckets, their rotation dates, or the specific bucket that an email gets assigned to. +::: + ## FAQs for DSPs Here are some frequently asked questions for demand-side platforms (DSPs). @@ -243,7 +261,7 @@ Here are some frequently asked questions for demand-side platforms (DSPs). - [How do I know which decryption key to apply to a UID2?](#how-do-i-know-which-decryption-key-to-apply-to-a-uid2) - [Where do I get the decryption keys?](#where-do-i-get-the-decryption-keys) - [How many decryption keys may be present in memory at any point?](#how-many-decryption-keys-may-be-present-in-memory-at-any-point) -- [How do I know if/when the salt bucket has rotated?](#how-do-i-know-ifwhen-the-salt-bucket-has-rotated) +- [How do I know when to refresh mapped raw UID2s?](#how-do-i-know-when-to-refresh-mapped-raw-uid2s) - [Should the DSP be concerned with latency?](#should-the-dsp-be-concerned-with-latency) - [How should the DSP maintain proper frequency capping with UID2?](#how-should-the-dsp-maintain-proper-frequency-capping-with-uid2) - [Will all user opt-out traffic be sent to the DSP?](#will-all-user-opt-out-traffic-be-sent-to-the-dsp) @@ -268,9 +286,13 @@ You can use one of the server-side SDKs (see [SDKs: Summary](../sdks/summary-sdk There may be thousands of decryption keys present in the system at any given point. -#### How do I know if/when the salt bucket has rotated? +#### How do I know when to refresh mapped raw UID2s? + +See [Advertisers and Data Providers section](#how-do-i-know-when-to-refresh-a-raw-uid2). + +#### How do I know if/when the raw UID2 has rotated? -The DSP is not privy to when the UID2 salt bucket rotates. This is similar to a DSP being unaware if users cleared their cookies. Salt bucket rotation has no significant impact on the DSP. +The DSP is not privy to when the raw UID2 rotates. This is similar to a DSP being unaware if users cleared their cookies. Raw UID2 rotation has no significant impact on the DSP. #### Should the DSP be concerned with latency? @@ -278,7 +300,7 @@ The UID2 service does not introduce latency into the bidding process. Any latenc #### How should the DSP maintain proper frequency capping with UID2? -The UID2 has the same chance as a cookie of becoming stale. Hence, the DSP can adapt the same infrastructure currently used for cookie or deviceID-based frequency capping for UID2. For details, see [How do I know when to refresh the UID2 due to salt bucket rotation?](#how-do-i-know-when-to-refresh-the-uid2-due-to-salt-bucket-rotation). +The UID2 has the same chance as a cookie of becoming stale. Hence, the DSP can adapt the same infrastructure currently used for cookie or deviceID-based frequency capping for UID2. For details, see [How do I know when to refresh a raw UID2?](#how-do-i-know-when-to-refresh-a-raw-uid2). #### Will all user opt-out traffic be sent to the DSP? diff --git a/docs/getting-started/gs-opt-out.md b/docs/getting-started/gs-opt-out.md index fe13493b7..5be66a84c 100644 --- a/docs/getting-started/gs-opt-out.md +++ b/docs/getting-started/gs-opt-out.md @@ -20,7 +20,7 @@ Within the UID2 ecosystem, there are two types of opt out: Because each participant has their own opt-out workflow, participants are mandated to respect a user's opted-out status and therefore not create a UID2 for any user who opted out from the participant. -For example, if a user opts out of a publisher's site, but has not opted out of UID2, the publisher should not generate a UID2 token for that user. +For example, if a user opts out of a publisher's site, but has not opted out of UID2, the publisher should not generate a UID2 token for that user. Consumers can always opt out of UID2 being used to show them personalized ads, in the [Transparency and Control Portal](https://www.transparentadvertising.com/). Choose email address or phone number, enter the data, and follow the prompts. @@ -48,7 +48,7 @@ The following steps provide a high-level outline of the opt-out workflow intende | Participant | Distribution Method | | :--- | :--- | - | Publishers | A publisher calling [POST /token/generate](../endpoints/post-token-generate.md) with the required `optout_check` parameter set to `1`, or [POST /token/refresh](../endpoints/post-token-refresh.md), receives the opt-out response instead of the UID2 token. | + | Publishers | A publisher calling [POST /token/generate](../endpoints/post-token-generate.md) with the required `optout_check` parameter set to `1`, or [POST /token/refresh](../endpoints/post-token-refresh.md), receives the opt-out response instead of the UID2 token. | | DSPs | The UID2 Operator Service distributes information on all opted-out users to DSPs via a webhook provided for the purpose. For details, see [Honor User Opt-Outs](../guides/dsp-guide#honor-user-opt-outs).
    DSPs can also check the opt-out status of raw UID2s using the [POST /optout/status](../endpoints/post-optout-status.md) endpoint. | | Advertisers and data providers | The UID2 Operator Service distributes opt-out information to advertisers and data providers via the [POST /identity/map](../endpoints/post-identity-map.md) endpoint. Another option is to check the opt-out status of raw UID2s using the [POST /optout/status](../endpoints/post-optout-status.md) endpoint. | | Sharers | UID2 sharers can check the opt-out status of raw UID2s using the [POST /optout/status](../endpoints/post-optout-status.md) endpoint. | diff --git a/docs/getting-started/gs-permissions.md b/docs/getting-started/gs-permissions.md index 1a62e37a6..fdaa9d9a8 100644 --- a/docs/getting-started/gs-permissions.md +++ b/docs/getting-started/gs-permissions.md @@ -18,7 +18,7 @@ For each UID2 participant that has API Key and Client Secret, the permissions ar If you're a publisher and are implementing UID2 on the client side, API permissions do not apply to you. Instead, you'll receive a different set of credentials that are specifically for generating a client-side token request. For details, see [Subscription ID and Public Key](gs-credentials.md#subscription-id-and-public-key). ::: -A participant can have one or several sets of API credentials with associated permissions. In cases where you have more than one API permission, you have the option to have a separate set of credentials for each permission or have a single set of credentials for all permissions. We recommend having a separate set of credentials for each permission. +A participant can have one or several sets of API credentials with associated permissions. In cases where you have m ore than one API permission, you have the option to have a separate set of credentials for each permission or have a single set of credentials for all permissions. We recommend having a separate set of credentials for each permission. The following table lists the key permissions, the types of participants that commonly use them, and a summary of the key associated activities. @@ -27,4 +27,4 @@ The following table lists the key permissions, the types of participants that co | Generator | Publishers | Permission to call the [POST /token/generate](../endpoints/post-token-generate.md), [POST /token/validate](../endpoints/post-token-validate.md), and [POST /token/refresh](../endpoints/post-token-refresh.md) endpoints, to generate UID2 tokens from DII and to refresh them, using one of these integration methods: | | Bidder | DSPs | Permission to decrypt UID2 tokens coming in from the bidstream from publishers into raw UID2s for bidding purposes. | | Sharer | Any participant type that takes part in UID2 sharing. For details, see [UID2 Sharing: Overview](../sharing/sharing-overview.md). | Permission to do both of the following: | -| Mapper | Advertisers
    Data Providers | Permission to use the [POST /identity/buckets](../endpoints/post-identity-buckets.md) endpoint to monitor rotated salt buckets and to use the [POST /identity/map](../endpoints/post-identity-map.md) endpoint to map multiple email addresses, phone numbers, or their respective hashes to their raw UID2s and salt bucket IDs. | +| Mapper | Advertisers
    Data Providers | Permission to call the following endpoints to map multiple email addresses, phone numbers, or their respective hashes to their raw UID2s, previous raw UID2s, and refresh timestamps: | diff --git a/docs/guides/images/advertiser-flow-endpoints-mermaid-v3.png b/docs/guides/images/advertiser-flow-endpoints-mermaid-v3.png new file mode 100644 index 000000000..4e6dd74f8 Binary files /dev/null and b/docs/guides/images/advertiser-flow-endpoints-mermaid-v3.png differ diff --git a/docs/guides/images/advertiser-flow-overview-mermaid-v3.png b/docs/guides/images/advertiser-flow-overview-mermaid-v3.png new file mode 100644 index 000000000..840bff733 Binary files /dev/null and b/docs/guides/images/advertiser-flow-overview-mermaid-v3.png differ diff --git a/docs/guides/images/resource/advertiser-flow-endpoints-mermaid.md.bak b/docs/guides/images/resource/advertiser-flow-endpoints-v2-mermaid.md.bak similarity index 99% rename from docs/guides/images/resource/advertiser-flow-endpoints-mermaid.md.bak rename to docs/guides/images/resource/advertiser-flow-endpoints-v2-mermaid.md.bak index 6223664e3..d26caabf0 100644 --- a/docs/guides/images/resource/advertiser-flow-endpoints-mermaid.md.bak +++ b/docs/guides/images/resource/advertiser-flow-endpoints-v2-mermaid.md.bak @@ -1,4 +1,4 @@ ->DSP: Send stored raw UID2s to DSPs to create audiences and conversions, or use them for measurement. + Note over ADP,DSP: 5. Monitor for raw UID2 refresh. + loop + ADP->>ADP: 5-a. Check current time against stored refresh timestamps. + ADP->>UID: 5-b. If refresh time reached, resend DII to the POST /identity/map endpoint for updated raw UID2. + UID->>ADP: 5-c. Store the new raw UID2 (u), refresh timestamp (r), and optionally previous UID2 (p) returned from the POST /identity/map endpoint. + end + Note over ADP,DSP: 6. Monitor for opt-out status. Remove all opt-outs from local storage and do not use. + loop + ADP->>UID: 6-a. Monitor for optout status using the POST /optout/status endpoint. + UID->>ADP: 6-b. Return optout status. + end + +%%URL: +%%https://mermaid.live/edit#pako:eNrNVU1v2kAQ_SujPSUSGDDYgA-RolBVHNKg0F4qLht7gFXxrrsfSWmU_97ZNeYjASnH-oDZ3Zk3b948rV9ZrgpkGQMw-NuhzHEi-ErzciGBnoprK3JRcWnhdjIDbuC2eEbaNKg7E245zLR6FgXqjwk_phOfQK8YHirU3KozUZP5rN78piyCInBfqUXbGfQi-IrSZyJo_hKgDCy1KmEynYIzQq7ArhGIgLTCbqHkFaAsKiWkjWpc_2yUqqBeEnj75oaQCL7NI5hTOHDQvn1jIVfSciE9sC9hVcCfPcy_Q6ep0jlThQAJlsA97BPBUrMYchvicOWuW1RnqdGswYqSyvGygitN25xIqMoKJflms4VK47NQbifeVXVNedZpiUXd_ec40fKitHFD8aCr5_CBnokuQvQjuOdSVG7j56M0aVc-CXkE2UzgTPJgp_yhPElNJ02OHxMJGmJDoPFsz8T7d67RU-CuEN7CdSs0SapoSFLT8uycCfMoYUmLErlxGkvci3WGY0INKimocMjZD3In0i7Re2vvtNpdwQaJd9fdGvNfkDutqVJQFfiK_EVOaxq6pPipVxNvqunyJJwWPF9j4V1lvEafc2xoxlUFSXbQ81D0yMlJOz92ssSX_9TN6emkqHZbOS8xt85E8IglxQORaY52t8hG5XwTJsFXGGgXCqSy3iyXxxsGkvrxvit6qHl0N9Wd1aed3enHK-pI9dSP-jEodAr6Xojml7XYSouCZVY7bLESdcn9kr36iAXzrscFy-gvEaVBLdhCvlEWXcA_lSqbRK3cas2yJd8YWtUO2X0O9rvcWTXfyrzJqaO-FF6GfRCG5X39aQlfmFCLZa_sD8t6vW6UxINBPOyP416aDvsttmXZoJ9ESTKIR6M0jodJmvTfWuxvoNeNRoNxf9BNe8M0SYfd4YjIkgyo75STlmXjt38Vsi5b \ No newline at end of file diff --git a/docs/guides/images/resource/advertiser-flow-overview-mermaid.md.bak b/docs/guides/images/resource/advertiser-flow-overview-v2-mermaid.md.bak similarity index 100% rename from docs/guides/images/resource/advertiser-flow-overview-mermaid.md.bak rename to docs/guides/images/resource/advertiser-flow-overview-v2-mermaid.md.bak diff --git a/docs/guides/images/resource/advertiser-flow-overview-v3-mermaid.mermaid b/docs/guides/images/resource/advertiser-flow-overview-v3-mermaid.mermaid new file mode 100644 index 000000000..a1e32049c --- /dev/null +++ b/docs/guides/images/resource/advertiser-flow-overview-v3-mermaid.mermaid @@ -0,0 +1,29 @@ +%%26/6/25: V3 API Update - Updated diagram to use V3 identity map API with refresh timestamps instead of salt bucket monitoring. + + sequenceDiagram + participant ADP as Advertiser/Data Provider + participant UID as UID2 Operator + participant DSP + Note over ADP,DSP: 1. Generate raw UID2s from DII. + loop + ADP->>UID: 1-a. Send a request containing DII using an SDK, Snowflake, AWS Entity Resolution, or HTTP endpoint. + UID->>ADP: 1-b. Receive the raw UID2 (u), refresh timestamp (r), and optionally previous UID2 (p). + end + Note over ADP,DSP: 2. Store raw UID2s and refresh timestamps. + Note over ADP,DSP: 3. Manipulate or combine raw UID2s. + Note over ADP,DSP: 4. Send raw UID2s to DSPs. + ADP-->>DSP: Send stored raw UID2s to DSPs to create audiences and conversions, or use them for measurement. + Note over ADP,DSP: 5. Monitor for raw UID2 refresh. + loop + ADP->>ADP: 5-a. Check current time against stored refresh timestamps. + ADP->>UID: 5-b. If refresh time reached, resend DII to get updated raw UID2. + UID->>ADP: 5-c. Store the new raw UID2, refresh timestamp, and optionally previous UID2. + end + Note over ADP,DSP: 6. Monitor for opt-out status. Remove all opt-outs from local storage and do not use. + loop + ADP->>UID: 6-a. Monitor for optout status using the POST optout/status endpoint. + UID->>ADP: 6-b. Return optout status. + end + +%% URL: +%% https://mermaid.live/edit#pako:eNqNVF1v2kAQ_Cure0ok44DBhvghUhSiFlVpUElVqeLlYh9win3n3gcpjfLfu2sbCAlU9Yt9552d3dm5e2GZzgVLGYAVv7xQmRhLvjS8nCvAp-LGyUxWXDm4Hk-BW7jO1wI3rTAXY-44TI1ey1yYj4DvkzEB8BXBfSUMd_pI1Hg2bTa_aidAY3JiCnA7hV4In4QipADDn-tUFhZGlzCeTMIGR0-hdQXNEsGdqyuMRHiHhzATKgcOhtqzDjKtHJdKqiWlAG_piyuYjb8EMFP6eVHwJxHA9Y8Z3Con3Qa-CasL76RWAWgDnx8epoBJKy2Va2tAOiRFaiJ9DBGSCbkW4Fb7wuHMnwdYx8IIuwInSyyHlxWcGdzmWKSuiIMXxQYqI9ZS-1a8s-q85UHak2JF2Csq_FYpyvqB0IYnU_RDuONKVr4gxbHXTJePUr1JudX8CHjQar2nd5qmu8XQYFCkOrYOtFTtkXh6Z0ZQCdznkkzZtIKzQ0aLItl6Et7WCpewwEUpuPVGlGI3lCM1xtigVhKJa8xuNK1ILZDctPNW46d6tDH56WYlsifIvDHIVKsKfImOQm9tGzql-KE7YzLKZHEQjguerUROPrGkEXkU5VgKB77KUZK9Xvukb9wXd7KtDch7Sjzv4o9479---w_PJYd6YqaO9iQEd97SMSgxHjD19ld7egud8aLWiy9FXUSuQWlHIz09hFq2hIbwjnTP2R5o6n16P3tof160P9-d2kPpkubgOm_UYc69DixgSyNzljrjRcBKYUpOS_ZCIXNGZhRzluInVoYaz9lcvSIKb7qfWpdboNF-uWLpghcWV81g23t3t8u907ONyraYJuo2p753QaJe3jV3eH2V11wsfWG_WdrrdcM4GgyiYf8y6iXJsB-wDUsH_TiM40E0GiVRNIyTuP8asD91ed1wNLjsD7pJb5jEybA7HGGx2LgwN9orhyn7r38BOSL5zQ \ No newline at end of file diff --git a/docs/guides/integration-advertiser-dataprovider-endpoints.md b/docs/guides/integration-advertiser-dataprovider-endpoints.md index 1f523e229..295ef05b3 100644 --- a/docs/guides/integration-advertiser-dataprovider-endpoints.md +++ b/docs/guides/integration-advertiser-dataprovider-endpoints.md @@ -38,13 +38,13 @@ At a high level, the steps for advertisers and data providers integrating with U 1. [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii) -2. [Store Raw UID2s and Salt Bucket IDs](#2-store-raw-uid2s-and-salt-bucket-ids) +2. [Store Raw UID2s and Refresh Timestamps](#2-store-raw-uid2s-and-refresh-timestamps) 3. [Manipulate or Combine Raw UID2s](#3-manipulate-or-combine-raw-uid2s) 4. [Send Stored Raw UID2s to DSPs to Create Audiences or Conversions](#4-send-stored-raw-uid2s-to-dsps-to-create-audiences-or-conversions) -5. [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s) +5. [Monitor for Raw UID2 Refresh](#5-monitor-for-raw-uid2-refresh) 6. [Monitor for Opt-Out Status](#6-monitor-for-opt-out-status) @@ -54,31 +54,31 @@ The following diagram outlines the steps that data collectors must complete to m DII refers to a user's normalized email address or phone number, or the normalized and SHA-256-hashed email address or phone number. -![Advertiser Flow](images/advertiser-flow-endpoints-mermaid.png) +![Advertiser Flow](images/advertiser-flow-endpoints-mermaid-v3.png) - + ### 1: Generate Raw UID2s from DII | Step | Endpoint | Description | | --- | --- | --- | | 1-a | [POST /identity/map](../endpoints/post-identity-map.md) request | Send a request containing DII to the identity mapping endpoint. | -| 1-b | [POST /identity/map](../endpoints/post-identity-map.md) response | The `advertising_id` (raw UID2) returned in the response can be used to target audiences on relevant DSPs.
    The response returns a user's raw UID2 and the corresponding `bucket_id` for the salt bucket. The salt assigned to the bucket rotates annually, which impacts the generated raw UID2. For details on how to check for salt bucket rotation, see [5: Monitor for salt bucket rotations related to your stored raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s). | - +| 1-b | [POST /identity/map](../endpoints/post-identity-map.md) response | The raw UID2 (`u` field) returned in the response can be used to target audiences on relevant DSPs.
    The response returns a user's raw UID2 (`u`), refresh timestamp (`r`), and optionally the previous raw UID2 (`p`) if the current UID2 was rotated within the last 90 days. Use the refresh timestamp to determine when to refresh the UID2. For details, see [5: Monitor for Raw UID2 Refresh](#5-monitor-for-raw-uid2-refresh). | -### 2: Store Raw UID2s and Salt Bucket IDs +### 2: Store Raw UID2s and Refresh Timestamps The response from Step 1, [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii), contains mapping information. We recommend that you store the following information returned in Step 1: -- Cache the mapping between DII (`identifier`), raw UID2 (`advertising_id`), and salt bucket (`bucket_id`). -- Store the timestamp for when you received the response data. Later, you can compare this timestamp with the `last_updated` timestamp returned in Step 5, [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s). +- Cache the mapping between DII and raw UID2 (`u` field). +- Store the refresh timestamp (`r` field) to know when the raw UID2 could refresh. +- Optionally store the previous raw UID2 (`p` field) if provided for users whose UID2 was refreshed within the last 90 days. ### 3: Manipulate or Combine Raw UID2s Use the UID2s you received in Step 1. For example, you might do one or more of the following: -- Do some manipulation: for example, combine UID2s you generated from DII and UID2s received from another participant such as an advertiser or data provider. -- Add new UID2s into an existing audience. +- Do some manipulation: for example, combine raw UID2s you generated from DII and raw UID2s received from another participant such as an advertiser or data provider. +- Add new raw UID2s into an existing audience. ### 4: Send Stored Raw UID2s to DSPs to Create Audiences or Conversions @@ -87,42 +87,37 @@ Use the raw UID2s for some purpose such as: - Sending stored raw UID2s to DSPs to create audiences and conversions. - Using the raw UID2s for measurement. -For example, you could send the `advertising_id` (raw UID2) returned in Step 1-b to a DSP while building your audiences. Each DSP has a unique integration process for building audiences; follow the integration guidance provided by the DSP for sending raw UID2s to build an audience. +For example, you could send the (raw UID2) (`u` field) returned in Step 1-b to a DSP while building your audiences. Each DSP has a unique integration process for building audiences; follow the integration guidance provided by the DSP for sending raw UID2s to build an audience. You could also send conversion information via API or pixels for measurement (attribution) or for retargeting. -### 5: Monitor for Salt Bucket Rotations for Your Stored Raw UID2s +### 5: Monitor for Raw UID2 Refresh -A raw UID2 is an identifier for a user at a specific moment in time. The raw UID2 for a specific user changes at least once per year, as a result of the salt bucket rotation. +A raw UID2 is an identifier for a user at a specific moment in time. The raw UID2 for a specific user changes roughly once per year as part of the UID2 refresh process. -Even though each salt bucket is updated approximately once per year, individual bucket updates are spread over the year. Approximately 1/365th of all salt buckets are rotated daily. Based on this, we recommend checking salt bucket rotation regularly, on a cadence that aligns with your audience refreshes. For example, if you refresh weekly, check for salt bucket updates weekly. +The v3 Identity Map API provides a refresh timestamp (`r` field) in the response that indicates when each raw UID2 might refresh. Use this timestamp to determine when to regenerate raw UID2s for your stored data, it is guaranteed that it won't refresh before that time. -If the salt bucket has been rotated, regenerate the raw UID2. For details, see [Determine whether the salt bucket has been rotated](#determine-whether-the-salt-bucket-has-been-rotated). - -The following table shows the steps for checking for salt bucket rotation. +We recommend checking for refresh opportunities daily. The following table shows the steps for monitoring raw UID2 refresh. -| Step | Endpoint | Description | -| --- | --- | --- | -| 5-a | [POST /identity/buckets](../endpoints/post-identity-buckets.md) | Send a request to the `POST /identity/buckets` endpoint for all salt buckets that have changed since a specific timestamp. | -| 5-b | [POST /identity/buckets](../endpoints/post-identity-buckets.md) | UID2 service: The `POST /identity/buckets` endpoint returns a list of `bucket_id` and `last_updated` timestamps. | -| 5-c | [POST /identity/map](../endpoints/post-identity-map.md) | Compare the returned `bucket_id` to the salt buckets of raw UID2s that you've cached.
    If you find that the salt bucket was updated for one or more raw UID2s, re-send the DII to the `POST /identity/map` endpoint for a new raw UID2. | -| 5-d | [POST /identity/map](../endpoints/post-identity-map.md) | Store the new values returned for `advertising_id` and `bucket_id`. | +| Step | Action | Description | +| :--- | :--- | :--- | +| 5-a | Local timestamp check | Compare the current time with the refresh timestamp (`r` field) you stored from the [POST /identity/map](../endpoints/post-identity-map.md) response previously. | +| 5-b | [POST /identity/map](../endpoints/post-identity-map.md) | If the current time is greater than or equal to the refresh timestamp, regenerate the raw UID2 by calling the identity map endpoint again with the same DII. | +| 5-c | Local storage update | Store the new raw UID2 (`u` field), refresh timestamp (`r` field) and optionally previous UID2 (`p` field) returned from the response. | -#### Determine whether the salt bucket has been rotated +#### Determine whether to refresh a raw UID2 -To determine whether the salt bucket ID for a specific raw UID2 has changed, follow these steps. +To determine whether to refresh a raw UID2, follow these steps: -1. Compare these two values: +1. Compare the current time with the refresh timestamp (`r` field) you stored from the [POST /identity/map](../endpoints/post-identity-map.md) response. - - The `last_updated` timestamp of each `bucket_id` returned as part of monitoring the salt bucket rotations. - - - The timestamp of the raw UID2 generation of the same `bucket_id`, which was returned in Step 1 and stored in Step 2. +2. If the current time is greater than or equal to the refresh timestamp, regenerate the raw UID2 by calling [POST /identity/map](../endpoints/post-identity-map.md) again with the same DII. -1. If the `last_updated` timestamp is more recent than the timestamp you recorded earlier, the salt bucket has been rotated. As a result, you'll need to regenerate any raw UID2s associated with this `bucket_id`, following Step 1, [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii). +This approach ensures your raw UID2s remain current and valid for audience targeting and measurement. ### 6: Monitor for Opt-Out Status -It's important to honor user opt-out status. Periodically, monitor for opt-out status, to be sure that you don't continue using UID2s for users that have recently opted out. +It's important to honor user opt-out status. Periodically, monitor for opt-out status, to be sure that you don't continue using raw UID2s for users that have recently opted out. There are two ways that you can check with the UID2 Operator Service to make sure you have the latest opt-out information: @@ -131,3 +126,68 @@ There are two ways that you can check with the UID2 salt bucket rotation. + +Even though each salt bucket is updated approximately once per year, individual bucket updates are spread over the year. Approximately 1/365th of all salt buckets are rotated daily. Based on this, we recommend checking salt bucket rotation regularly, on a cadence that aligns with your audience refreshes. For example, if you refresh weekly, check for salt bucket updates weekly. + +If the salt bucket has been rotated, regenerate the raw UID2. For details, see [Determine whether the salt bucket has been rotated](#determine-whether-the-salt-bucket-has-been-rotated). + +The following table shows the steps for checking for salt bucket rotation. + +| Step | Endpoint | Description | +| --- | --- | --- | +| 5-a | [POST /identity/buckets](../endpoints/post-identity-buckets.md) | Send a request to the `POST /identity/buckets` endpoint for all salt buckets that have changed since a specific timestamp. | +| 5-b | [POST /identity/buckets](../endpoints/post-identity-buckets.md) | UID2 service: The `POST /identity/buckets` endpoint returns a list of `bucket_id` and `last_updated` timestamps. | +| 5-c | [POST /identity/map](../endpoints/post-identity-map-v2.md) | Compare the returned `bucket_id` to the salt buckets of raw UID2s that you've cached.
    If you find that the salt bucket was updated for one or more raw UID2s, re-send the DII to the `POST /identity/map` endpoint for a new raw UID2. | +| 5-d | [POST /identity/map](../endpoints/post-identity-map-v2.md) | Store the new values returned for `advertising_id` and `bucket_id`. | + +##### Determine whether the salt bucket has been rotated + +To determine whether the salt bucket ID for a specific raw UID2 has changed, follow these steps. + +1. Compare these two values: + + - The `last_updated` timestamp of each `bucket_id` returned as part of monitoring the salt bucket rotations. + + - The timestamp of the raw UID2 generation of the same `bucket_id`, which was returned in Step 1 and stored in Step 2. + +1. If the `last_updated` timestamp is more recent than the timestamp you recorded earlier, the salt bucket has been rotated. As a result, you'll need to regenerate any raw UID2s associated with this `bucket_id`, following Step 1, [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii). diff --git a/docs/guides/integration-advertiser-dataprovider-overview.md b/docs/guides/integration-advertiser-dataprovider-overview.md index 651abf6f2..ad77d0850 100644 --- a/docs/guides/integration-advertiser-dataprovider-overview.md +++ b/docs/guides/integration-advertiser-dataprovider-overview.md @@ -23,9 +23,9 @@ There are other ways that you can use UID2, outside these use cases. These are j | Send/Receive? | Action | Advantage/Result | | --- | --- | --- | -| Send in audiences | Send UID2s via API or pixels | Create audiences. | -| Send in conversions | Send UID2s as conversion information | Use conversion information for measurement (attribution) or for retargeting via API or pixels. | -| Receive graph data | Receive UID2s from graph/data providers via API or pixels | Build graph data. | +| Send in audiences | Send raw UID2s via API or pixels | Create audiences. | +| Send in conversions | Send raw UID2s as conversion information | Use conversion information for measurement (attribution) or for retargeting via API or pixels. | +| Receive graph data | Receive raw UID2s from graph/data providers via API or pixels | Build graph data. | ## High-Level Steps @@ -33,16 +33,20 @@ At a high level, the steps for advertisers and data providers integrating with U 1. [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii) -2. [Store Raw UID2s and Salt Bucket IDs](#2-store-raw-uid2s-and-salt-bucket-ids) +2. [Store Raw UID2s and Refresh Timestamps](#2-store-raw-uid2s-and-refresh-timestamps) 3. [Manipulate or Combine Raw UID2s](#3-manipulate-or-combine-raw-uid2s) 4. [Send Stored Raw UID2s to DSPs to Create Audiences or Conversions](#4-send-stored-raw-uid2s-to-dsps-to-create-audiences-or-conversions) -5. [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s) +5. [Monitor for Raw UID2 Refresh](#5-monitor-for-raw-uid2-refresh) 6. [Monitor for Opt-Out Status](#6-monitor-for-opt-out-status) +:::note +If your implementation uses a version of the [POST /identity/map](../endpoints/post-identity-map.md) endpoint earlier than version 3, see [Using POST /identity/map Version 2](#using-post-identitymap-version-2). If you're using this version, we recommend you upgrade as soon as possible to take advantage of the enhancements. +::: + ## Summary of Implementation Options The following table shows the implementation options that are available for advertisers and data providers, for each of the high-level steps. Some steps are managed solely as part of your own custom implementation; some steps can be managed by one or more of the UID2 implementation options available. For details, click the link on each step. @@ -50,10 +54,10 @@ The following table shows the implementation options that are available for adve | High-Level Step | Implementation Options | | --- | --- | | [1: Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii) | Use any of the following options to map DII to raw UID2s: | -| [2: Store Raw UID2s and Salt Bucket IDs](#2-store-raw-uid2s-and-salt-bucket-ids) | Custom (your choice). | +| [2: Store Raw UID2s and Refresh Timestamps](#2-store-raw-uid2s-and-refresh-timestamps) | Custom (your choice). | | [3: Manipulate or Combine Raw UID2s](#3-manipulate-or-combine-raw-uid2s) | Custom (your choice). | | [4: Send Stored Raw UID2s to DSPs to Create Audiences or Conversions](#4-send-stored-raw-uid2s-to-dsps-to-create-audiences-or-conversions) | Custom (your choice). | -| [5: Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s) | Any of the following options: | +| [5: Monitor for Raw UID2 Refresh](#5-monitor-for-raw-uid2-refresh) | Use the refresh timestamp (`r` field) returned from the [POST /identity/map](../endpoints/post-identity-map.md) endpoint to determine when to refresh Raw UID2s. | | [6: Monitor for Opt-Out Status](#6-monitor-for-opt-out-status) | API call to the [POST /optout/status](../endpoints/post-optout-status.md) endpoint. | ## Integration Diagram @@ -64,9 +68,9 @@ DII refers to a user's normalized email address or phone number, or the normaliz To keep your UID2-based audience information accurate and up to date, follow these integration steps every day. -![Advertiser Flow](images/advertiser-flow-overview-mermaid.png) +![Advertiser Flow](images/advertiser-flow-overview-mermaid-v3.png) - + For details about the different parts of the diagram, refer to the following sections. @@ -87,19 +91,20 @@ To generate raw UID2s, use one of the following options: - HTTP endpoints: [POST /identity/map](../endpoints/post-identity-map.md). For details, see [Generate Raw UID2s from DII](integration-advertiser-dataprovider-endpoints.md#1-generate-raw-uid2s-from-dii). -### 2: Store Raw UID2s and Salt Bucket IDs +### 2: Store Raw UID2s and Refresh Timestamps The response from Step 1, [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii), contains mapping information. We recommend that you store the following information returned in Step 1: -- Cache the mapping between DII (`identifier`), raw UID2 (`advertising_id`), and salt bucket (`bucket_id`). -- Store the timestamp for when you received the response data. Later, you can compare this timestamp with the `last_updated` timestamp returned in Step 5, [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s). +- Cache the mapping between DII and raw UID2 (`u` field). +- Store the refresh timestamp (`r` field) to know when the raw UID2 could refresh. +- Optionally store the previous raw UID2 (`p` field) if provided for users whose UID2 was refreshed within the last 90 days. ### 3: Manipulate or Combine Raw UID2s -Use the UID2s you received in Step 1. For example, you might do one or more of the following: +Use the raw UID2s you received in Step 1. For example, you might do one or more of the following: -- Do some manipulation: for example, combine UID2s you generated from DII and UID2s received from another participant such as an advertiser or data provider. -- Add new UID2s into an existing audience. +- Do some manipulation: for example, combine raw UID2s you generated from DII and raw UID2s received from another participant such as an advertiser or data provider. +- Add new raw UID2s into an existing audience. ### 4: Send Stored Raw UID2s to DSPs to Create Audiences or Conversions @@ -108,17 +113,79 @@ Use the raw UID2s for some purpose such as: - Sending stored raw UID2s to DSPs to create audiences and conversions. - Using the raw UID2s for measurement. -For example, you could send the `advertising_id` (raw UID2) returned in Step 1-b to a DSP while building your audiences. Each DSP has a unique integration process for building audiences; follow the integration guidance provided by the DSP for sending raw UID2s to build an audience. +For example, you could send the (raw UID2 (`u` field) returned in Step 1 to a DSP while building your audiences. Each DSP has a unique integration process for building audiences; follow the integration guidance provided by the DSP for sending raw UID2s to build an audience. You could also send conversion information via API or pixels for measurement (attribution) or for retargeting. -### 5: Monitor for Salt Bucket Rotations for Your Stored Raw UID2s +### 5: Monitor for Raw UID2 Refresh + +A raw UID2 is an identifier for a user at a specific moment in time. The raw UID2 for a specific user changes roughly once per year as part of the UID2 refresh process. + +The v3 Identity Map API provides a refresh timestamp (`r` field) in the response that indicates when each raw UID2 might refresh. Use this timestamp to determine when to regenerate raw UID2s for your stored data. It is guaranteed that it won't refresh before that time. + +We recommend checking for refresh opportunities daily. To determine whether to refresh a raw UID2: + +1. Compare the current time with the refresh timestamp (`r` field) you stored from the [POST /identity/map](../endpoints/post-identity-map.md) response. + +2. If the current time is greater than or equal to the refresh timestamp, regenerate the raw UID2 by calling [POST /identity/map](../endpoints/post-identity-map.md) again with the same DII. + +This approach ensures your raw UID2s remain current and valid for audience targeting and measurement. + +### 6: Monitor for Opt-Out Status + +It's important to honor user opt-out status. Periodically, monitor for opt-out status, to be sure that you don't continue using raw UID2s for users that have recently opted out. + +There are two ways that you can check with the UID2 Operator Service to make sure you have the latest opt-out information: + +- Call the [POST /identity/map](../endpoints/post-identity-map.md) endpoint to check for opt-outs. If the DII has been opted out, no raw UID2 is generated. + +- Check the opt-out status of raw UID2s using the [POST /optout/status](../endpoints/post-optout-status.md) endpoint. + +For details about the UID2 opt-out workflow and how users can opt out, see [User Opt-Out](../getting-started/gs-opt-out.md). + +## Using POST /identity/map Version 2 + +:::note +The following information is relevant only to integration approaches that use an earlier version of the `POST /identity/map` endpoint, version 2, and is provided for reference only. New implementations should use the latest version: see [High-Level Steps](#high-level-steps). +::: + +The key differences when using v2 of the Identity Map API are: + +- **Step 2**: Store salt bucket IDs instead of refresh timestamps +- **Step 5**: Monitor for salt bucket rotations instead of using refresh timestamps + +All other steps (1, 3, 4, and 6) are the same as described in the v3 implementation: see [High-Level Steps](#high-level-steps). -A raw UID2 is an identifier for a user at a specific moment in time. The raw UID2 for a specific user changes at least once per year, as a result of the salt bucket rotation. +### Integration Diagram (v2) + +The following diagram outlines the v2 integration flow. Note that the main differences are in Step 2 (storing salt bucket IDs) and Step 5 (monitoring salt bucket rotations). + +![Advertiser Flow](images/advertiser-flow-overview-mermaid.png) + + + +### Store Raw UID2s and Salt Bucket IDs (v2) + +:::note +This step replaces Step 2 in the v3 implementation. +::: + +The response from Step 1 contains mapping information. We recommend that you store the following information returned in Step 1: + +- Cache the mapping between DII (`identifier`), raw UID2 (`advertising_id`), and salt bucket (`bucket_id`). +- Store the timestamp for when you received the response data. Later, you can compare this timestamp with the `last_updated` timestamp returned in Step 5. + +### Monitor for Salt Bucket Rotations for Your Stored Raw UID2s (v2) + +:::note +This step replaces Step 5 in the v3 implementation. +::: + +A raw UID2 is an identifier for a user at a specific moment in time. The raw UID2 for a specific user changes roughly once per year, as a result of the salt bucket rotation. Even though each salt bucket is updated approximately once per year, individual bucket updates are spread over the year. Approximately 1/365th of all salt buckets are rotated daily. Based on this, we recommend checking salt bucket rotation regularly, on a cadence that aligns with your audience refreshes. For example, if you refresh weekly, check for salt bucket updates weekly. -If the salt bucket has been rotated, regenerate the raw UID2. For details, see [Determine whether the salt bucket has been rotated](#determine-whether-the-salt-bucket-has-been-rotated). +If the salt bucket has been rotated, regenerate the raw UID2. For details, see [Determine whether the salt bucket has been rotated](#determine-whether-the-salt-bucket-has-been-rotated-v2). For instructions for monitoring for salt bucket rotations, refer to one of the following: @@ -126,35 +193,23 @@ For instructions for monitoring for salt bucket rotations, refer to one of the f - Snowflake: [Monitor for Salt Bucket Rotation and Regenerate Raw UID2s](integration-snowflake.md#monitor-for-salt-bucket-rotation-and-regenerate-raw-uid2s). -- HTTP endpoints: [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](integration-advertiser-dataprovider-endpoints.md#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s). +- HTTP endpoints: [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s (v2)](integration-advertiser-dataprovider-endpoints.md#monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s-v2). :::note For AWS Entity Resolution, there is no way to do salt bucket monitoring. As an alternative, you could regenerate raw UID2s periodically using the AWS Entity Resolution service. ::: -#### Determine whether the salt bucket has been rotated +##### Determine whether the salt bucket has been rotated (v2) To determine whether the salt bucket ID for a specific raw UID2 has changed, follow these steps. 1. Compare these two values: - - The `last_updated` timestamp of each `bucket_id` returned as part of monitoring the salt bucket rotations (whatever option you choose). - - - The timestamp of the raw UID2 generation of the same `bucket_id`, which was returned in Step 1 and stored in Step 2. - -1. If the `last_updated` timestamp is more recent than the timestamp you recorded earlier, the salt bucket has been rotated. As a result, you'll need to regenerate any raw UID2s associated with this `bucket_id`, following Step 1, [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii). - -### 6: Monitor for Opt-Out Status - -It's important to honor user opt-out status. Periodically, monitor for opt-out status, to be sure that you don't continue using UID2s for users that have recently opted out. + - The `last_updated` timestamp of each `bucket_id` returned as part of monitoring the salt bucket rotations (whatever option you choose). -There are two ways that you can check with the UID2 Operator Service to make sure you have the latest opt-out information: - -- Call the [POST /identity/map](../endpoints/post-identity-map.md) endpoint to check for opt-outs. If the DII has been opted out, no raw UID2 is generated. + - The timestamp of the raw UID2 generation of the same `bucket_id`, which was returned in Step 1 and stored in Step 2. -- Check the opt-out status of raw UID2s using the [POST /optout/status](../endpoints/post-optout-status.md) endpoint. - -For details about the UID2 opt-out workflow and how users can opt out, see [User Opt-Out](../getting-started/gs-opt-out.md). +1. If the `last_updated` timestamp is more recent than the timestamp you recorded earlier, the salt bucket has been rotated. As a result, you'll need to regenerate any raw UID2s associated with this `bucket_id`, following Step 1, [Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii). ## FAQs diff --git a/docs/overviews/images/UID2AdvertiserAndThirdPartyDataProviderWorkflow.svg b/docs/overviews/images/UID2AdvertiserAndThirdPartyDataProviderWorkflow.svg index 477af3b9c..408dd424e 100644 --- a/docs/overviews/images/UID2AdvertiserAndThirdPartyDataProviderWorkflow.svg +++ b/docs/overviews/images/UID2AdvertiserAndThirdPartyDataProviderWorkflow.svg @@ -1,30 +1,30 @@ - - + + - - + + - - - - - - - - + + + + + + + + - + - - + + diff --git a/docs/overviews/images/UID2PublisherAndSSPWorkflow.svg b/docs/overviews/images/UID2PublisherAndSSPWorkflow.svg index c28ab5304..7d9c3ed09 100644 --- a/docs/overviews/images/UID2PublisherAndSSPWorkflow.svg +++ b/docs/overviews/images/UID2PublisherAndSSPWorkflow.svg @@ -3,7 +3,7 @@ - + @@ -20,9 +20,9 @@ - + - + @@ -60,7 +60,7 @@ - + @@ -86,7 +86,7 @@ - - + + diff --git a/docs/overviews/images/source/source.txt b/docs/overviews/images/source/source.txt index 8aab7fab4..d4cd1c6e4 100644 --- a/docs/overviews/images/source/source.txt +++ b/docs/overviews/images/source/source.txt @@ -4,4 +4,6 @@ Source is the same as for the workflow diagrams in docs/overviews/images, for UI https://www.figma.com/file/43lE7yuZ2NWflmBdcJNBkw/UID2-Workflows?type=design&node-id=28%3A2&mode=design&t=ehOn5ACoXYTyK6aU-1 -updated 6 May 2024 \ No newline at end of file +updated 6 May 2024 + +Last update 9 July 25 for POST /identity/map updates \ No newline at end of file diff --git a/docs/overviews/overview-advertisers.md b/docs/overviews/overview-advertisers.md index 11042bba0..bd4b5151d 100644 --- a/docs/overviews/overview-advertisers.md +++ b/docs/overviews/overview-advertisers.md @@ -39,15 +39,15 @@ Here are just some of the intended benefits of using UID2 as part of your advert The following steps provide a high-level outline of the workflow intended for organizations that collect user data and push it to DSPs—for example, advertisers, identity graph providers, and third-party data providers. The following process occurs in the background: -* The advertiser or data provider monitors the UID2 Operator for rotated salt buckets and updates UID2s as needed. +* The advertiser or data provider monitors refresh timestamps and updates UID2s when the current time exceeds the refresh timestamp for each stored UID2. The following steps are an example of how an advertiser can integrate with UID2: 1. The advertiser sends a user’s directly identifying information (DII) to the UID2 Operator. -2. The UID2 Operator generates and returns a raw UID2 and salt bucket ID. -3. The advertiser stores the UID2 and salt bucket ID and sends the UID2-based first-party and third-party audience segments to the DSP. +2. The UID2 Operator generates and returns a raw UID2 and refresh timestamp. +3. The advertiser stores the UID2 and refresh timestamp and sends the UID2-based first-party and third-party audience segments to the DSP. - Server-side: The advertiser stores the UID2 in a mapping table, DMP, data lake, or other server-side application. + Server-side: The advertiser stores the UID2 and refresh timestamp in a mapping table, DMP, data lake, or other server-side application. ![Data Provider Workflow](images/UID2AdvertiserAndThirdPartyDataProviderWorkflow.svg) diff --git a/docs/overviews/overview-data-providers.md b/docs/overviews/overview-data-providers.md index 5c17c580c..9da97cbfa 100644 --- a/docs/overviews/overview-data-providers.md +++ b/docs/overviews/overview-data-providers.md @@ -44,15 +44,15 @@ Here are just some of the intended benefits available to you as a data provider The following steps provide a high-level outline of the workflow intended for organizations that collect user data and push it to DSPs—for example, advertisers, identity graph providers, and third-party data providers. The following process occurs in the background: -* The advertiser or data provider monitors the UID2 Operator for rotated salt buckets, and updates UID2s as needed. +* The advertiser or data provider monitors refresh timestamps and updates UID2s when the current time exceeds the refresh timestamp for each stored UID2. The following steps are an example of how a data provider can integrate with UID2: 1. The data provider sends a user’s directly identifying information (DII) to the UID2 Operator. -2. The UID2 Operator generates and returns a raw UID2 and salt bucket ID. -3. The data provider stores the UID2 and salt bucket ID and sends the UID2-based first-party and third-party audience segments to the DSP. +2. The UID2 Operator generates and returns a raw UID2 and refresh timestamp. +3. The data provider stores the UID2 and refresh timestamp and sends the UID2-based first-party and third-party audience segments to the DSP. - Server-side: The data provider stores the UID2 in a mapping table, DMP, data lake, or other server-side application. + Server-side: The data provider stores the UID2 and refresh timestamp in a mapping table, DMP, data lake, or other server-side application. ![Data Provider Workflow](images/UID2AdvertiserAndThirdPartyDataProviderWorkflow.svg) diff --git a/docs/ref-info/glossary-uid.md b/docs/ref-info/glossary-uid.md index 51a13e752..2c2c663f2 100644 --- a/docs/ref-info/glossary-uid.md +++ b/docs/ref-info/glossary-uid.md @@ -91,6 +91,7 @@ import MdxJumpAnchor from '@site/src/components/MdxJumpAnchor'; **R** Raw UID2 | +Refresh timestamp | Refresh token **S** @@ -372,7 +373,7 @@ import MdxJumpAnchor from '@site/src/components/MdxJumpAnchor';
    Participant
    -
    An entity that fulfils a key role in UID2. Participants include the following: Core Administrator, Operator, DSP, data provider, advertiser, publisher, consumer.
    +
    An entity that fulfils a key role in UID2. Participants include the following: Core Administrator, Operator, DSP, data provider, advertiser, publisher, and consumer.
    For details, see participants.
    Private Operator
    @@ -400,6 +401,10 @@ import MdxJumpAnchor from '@site/src/components/MdxJumpAnchor';
    An unencrypted alphanumeric identifier created through the UID2 APIs or SDKs with the user's directly identifying information (email address or phone number) as input. The raw UID2 is encrypted to create a UID2 token. The raw UID2 is a unique value; no two raw UID2s are the same. Raw UID2s, and their associated UID2 tokens, are case sensitive.
    For details, see UID2 Identifier Types.
    +
    Refresh timestamp
    +
    In the context of mapping DII to raw UID2s, a refresh timestamp is a Unix timestamp (in seconds) returned in the r field of the POST /identity/map endpoint response. The raw UID2 is guaranteed to be valid until this timestamp. It is refreshed at some point after this time.
    +
    Use the refresh timestamp to determine when to regenerate raw UID2s for your stored data. We recommend checking for refresh opportunities daily by comparing the current time with the stored refresh timestamps.
    +
    Refresh token
    A refresh token is an opaque string that is issued along with the UID2 token. It is used to refresh the UID2 token, which has a limited life.
    When the UID2 server receives the refresh token with a request for a new UID2 token, it checks for user opt-out. If the user has opted out of UID2, no new UID2 token is generated.
    @@ -418,10 +423,12 @@ import MdxJumpAnchor from '@site/src/components/MdxJumpAnchor';
    Salt bucket
    A salt bucket is used to manage secret salt values, used to generate raw UID2s or UID2 tokens, over time. Each bucket contains a single current salt value, which remains active for approximately one year before being rotated to a new value. Buckets can be updated independently of one another.
    There are just over one million salt buckets, and each email address or phone number is assigned to a specific bucket in a deterministic manner. However, this assignment is not permanent; it might change when the bucket's current salt is rotated to a new value.
    +
    In versions of the [POST /identity/map](../endpoints/post-identity-map.md) endpoint earlier than version 3, such as [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md), the endpoint returns salt bucket IDs. In v3 and later, salt bucket information is not needed.
    Salt bucket ID
    A salt bucket ID is a unique string of characters that identifies a specific salt bucket. The salt bucket ID can be used to check which salt buckets have recently had their salt values updated, indicating which emails or phone numbers need their raw UID2 values regenerated.
    -
    For an example of a salt bucket ID, see the response to the `POST /identity/buckets` endpoint: Decrypted JSON Response Format.
    +
    In versions of the [POST /identity/map](../endpoints/post-identity-map.md) endpoint earlier than version 3, such as [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md), the endpoint returns salt bucket IDs. In v3 and later, salt bucket information is not needed.
    +
    For an example of a salt bucket ID, see the response to the `POST /v2/identity/buckets` endpoint: Decrypted JSON Response Format. If you're using `POST /v3/identity/map`, you don't need to use `POST /v2/identity/buckets` at all.
    Salted hash
    When a salt value is added to the input string before applying the hash function, the result is a salted hash. When the input value is salted before hashing, an attacker who has the hash cannot determine the input value by trying many possible inputs to arrive at the same output.
    diff --git a/docs/ref-info/images/UID2Workflows.svg b/docs/ref-info/images/UID2Workflows.svg index 04c601011..da56f41e7 100644 --- a/docs/ref-info/images/UID2Workflows.svg +++ b/docs/ref-info/images/UID2Workflows.svg @@ -6,8 +6,8 @@ - - + + @@ -25,9 +25,9 @@ - - - + + + @@ -49,16 +49,16 @@ - - + + - - + + - - + + @@ -96,9 +96,9 @@ - + - + @@ -165,7 +165,7 @@ - - + + diff --git a/docs/ref-info/updates-doc.md b/docs/ref-info/updates-doc.md index 62772d0e3..d04df3f7b 100644 --- a/docs/ref-info/updates-doc.md +++ b/docs/ref-info/updates-doc.md @@ -20,6 +20,22 @@ Check out the latest updates to our UID2 documentation resources. Use the Tags toolbar to view a subset of documentation updates. ::: +## Q3 2025 + +The following documents were released in this quarter. + + + +### Identity Map v3 + +July 11, 2025 + +We've released a new version of the Identity Map API (v3) that provides significant improvements for advertisers and data providers. This update includes documentation for the [POST /identity/map](../endpoints/post-identity-map.md) endpoint, the [SDK for Java Reference Guide](../sdks/sdk-ref-java.md#usage-for-advertisersdata-providers), and the [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md). + + + + + ## Q1 2025 The following documents were released in this quarter. @@ -423,7 +439,7 @@ For details, see [UID2 Hashing Tool](../getting-started/gs-normalization-encodin February 28, 2024 -The Java SDK now supports Advertisers and Data Providers wanting to use the [POST /identity/map](../endpoints/post-identity-map.md) endpoint. +The Java SDK now supports Advertisers and Data Providers wanting to use the [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) endpoint. For details, see the updated documentation in the *SDK for Java Reference Guide*: [Usage for Advertisers and Data Providers](../sdks/sdk-ref-java.md#usage-for-advertisersdata-providers). diff --git a/docs/sdks/sdk-ref-java.md b/docs/sdks/sdk-ref-java.md index 5b4abc707..392d9e5c9 100644 --- a/docs/sdks/sdk-ref-java.md +++ b/docs/sdks/sdk-ref-java.md @@ -145,7 +145,7 @@ If you're using the SDK's HTTP implementation, follow these steps. 2. Call a function that takes the user's email address or phone number as input and generates a `TokenGenerateResponse` object. The following example uses an email address: ```java - TokenGenerateResponse tokenGenerateResponse = publisherUid2Client.generateTokenResponse(TokenGenerateInput.fromEmail(emailAddress).doNotGenerateTokensForOptedOut()); + TokenGenerateResponse tokenGenerateResponse = publisherUid2Client.generateTokenResponse(TokenGenerateInput.fromEmail("user@example.com").doNotGenerateTokensForOptedOut()); ``` :::important @@ -193,7 +193,9 @@ If you're using server-side integration (see [Publisher Integration Guide, Serve 2. Determine if the identity can be refreshed (that is, the refresh token hasn't expired): ```java - if (identity == null || !identity.isRefreshable()) { we must no longer use this identity (for example, remove this identity from the user's session) } + if (identity == null || !identity.isRefreshable()) { + // we must no longer use this identity (for example, remove this identity from the user's session) + } ``` 3. Determine if a refresh is needed: @@ -220,13 +222,13 @@ If you're using server-side integration (see [Publisher Integration Guide, Serve 2. Call a function that takes the user's email address or phone number as input and creates a secure request data envelope. See [Encrypting requests](../getting-started/gs-encryption-decryption.md#encrypting-requests). The following example uses an email address: ```java - EnvelopeV2 envelope = publisherUid2Helper.createEnvelopeForTokenGenerateRequest(TokenGenerateInput.fromEmail(emailAddress).doNotGenerateTokensForOptedOut()); + EnvelopeV2 envelope = publisherUid2Helper.createEnvelopeForTokenGenerateRequest(TokenGenerateInput.fromEmail("user@example.com").doNotGenerateTokensForOptedOut()); ``` 3. Using an HTTP client library of your choice, post this envelope to the [POST token/generate](../endpoints/post-token-generate.md) endpoint, including headers and body: 1. Headers: Depending on your HTTP library, this might look something like the following: `.putHeader("Authorization", "Bearer " + UID2_API_KEY)` - `.putHeader("X-UID2-Client-Version", PublisherUid2Helper.getVersionHeader())` + `.putHeader("X-UID2-Client-Version", PublisherUid2Helper.getVersionHttpHeader())` 2. Body: `envelope.getEnvelope()` :::important diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/post-identity-map-v2.md b/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/post-identity-map-v2.md new file mode 100644 index 000000000..95f88bf30 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/post-identity-map-v2.md @@ -0,0 +1,199 @@ +--- +title: POST /identity/map (v2) +description: DII を raw UID2 とソルトバケット ID にマッピング。 +hide_table_of_contents: false +sidebar_position: 07 +--- + +import Link from '@docusaurus/Link'; + +# POST /identity/map (v2) + +複数のメールアドレス、電話番号、またはそれぞれのハッシュを、raw UID2 と salt bucket IDs にマッピングします。このエンドポイントを使用して、オプトアウト情報の更新をチェックすることもできます + +Used by: このエンドポイントは、主に広告主やデータプロバイダーが使用します。詳細は [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) を参照してください。 + +UID2 の Opt-Out ワークフローとユーザーが Opt-Out する方法の詳細は、[User Opt-Out](../getting-started/gs-opt-out.md) を参照してください。 + +## Batch Size and Request Parallelization Requirements + +知っておくべきことは以下のとおりです: + +- リクエストの最大サイズは 1MB です。 +- 大量のメールアドレス、電話番号、またはそれぞれのハッシュをマップするには、1 バッチあたり最大 5,000 アイテムのバッチサイズで、それらを *連続した* バッチで送信してください。 +- Private Operator を使用している場合を除き、バッチを並行して送信しないでください。つまり、単一の HTTP 接続を使用して、[directly identifying information (DII)](../ref-info/glossary-uid.md#gl-dii) を連続してマッピングしてください。 +- メールアドレス、電話番号、またはそれぞれのハッシュのマッピングを必ず保存してください。
    マッピングを保存しないと、数百万のメールアドレスや電話番号をマッピングする必要がある場合に、処理時間が大幅に増加する可能性があります。しかし、実際に更新が必要なマッピングのみを再計算することで、毎日更新が必要な raw UID2 の数は約 1/365 となり、総処理時間を短縮できます。[Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) と [FAQs for Advertisers and Data Providers](../getting-started/gs-faqs.md#faqs-for-advertisers-and-data-providers) も参照してください。 + +## Request Format + +`POST '{environment}/v2/identity/map'` + +認証の詳細は、 [Authentication and Authorization](../getting-started/gs-auth.md) を参照してください。 + +:::important +すべてのリクエストを秘密鍵で暗号化する必要があります。詳細といくつかのプログラミング言語でのコードの例は、[リクエストの暗号化とレスポンスの復号化](../getting-started/gs-encryption-decryption.md) を参照してください。 +::: + +### Path Parameters + +| Path Parameter | Data Type | Attribute | Description | +| :--- | :--- | :--- | :--- | +| `{environment}` | string | 必須 | テスト (インテグレーション) 環境: `https://operator-integ.uidapi.com`
    本番環境: `https://prod.uidapi.com`
    リージョンごとのオペレーターを含む全リストは [Environments](../getting-started/gs-environments.md) を参照してください。 | + +:::note +インテグレーション環境と本番環境では、異なる API Key が必要です。各環境の認証情報を取得する方法については、[Getting Your Credentials](../getting-started/gs-credentials.md#getting-your-credentials) を参照してください。 +::: + +### Unencrypted JSON Body Parameters + +:::important +リクエストを暗号化するときは、以下の 4 つの条件パラメータのうち、**1つ** だけをリクエストの JSON ボディにキーと値のペアとして含める必要がります。 +::: + +| Body Parameter | Data Type | Attribute | Description | +| :--- | :--- | :--- | :--- | +| `email` | string array | 条件付きで必要 | マッピングするメールアドレスのリストです。 | +| `email_hash` | string array | 条件付きで必要 | マッピングする [SHA-256 ハッシュし、Base64 エンコード](../getting-started/gs-normalization-encoding.md#email-address-normalization) した [正規化](../getting-started/gs-normalization-encoding.md#email-address-hash-encoding) 済みメールアドレスのリストです。 | +| `phone` | string array | 条件付きで必要 | マッピングする [正規化](../getting-started/gs-normalization-encoding.md#phone-number-normalization) 済み電話番号のリストです。 | +| `phone_hash` | string array | 条件付きで必要 | マッピングする [SHA-256 ハッシュし、Base64 エンコード](../getting-started/gs-normalization-encoding.md#phone-number-hash-encoding) した [正規化](../getting-started/gs-normalization-encoding.md#phone-number-normalization) 済み電話番号のリストです。 | + +### Request Examples + +以下は、各パラメータの暗号化されていない JSON リクエストボディの例です。このうちの 1 つを、`POST /identity/map` エンドポイントへのリクエストに含める必要があります: + +```json +{ + "email": [ + "user@example.com", + "user2@example.com" + ] +} +``` +```json +{ + "email_hash": [ + "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=", + "KzsrnOhCq4tqbGFMsflgS7ig1QLRr0nFJrcrEIlOlbU=" + ] +} +``` +```json +{ + "phone": [ + "+12345678901", + "+441234567890" + ] +} +``` +```json +{ + "phone_hash": [ + "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "Rx8SW4ZyKqbPypXmswDNuq0SPxStFXBTG/yvPns/2NQ=" + ] +} +``` + +以下は、電話番号に対する `POST /identity/map` エンドポイントへの暗号化リクエストの例です: + +```sh +echo '{"phone": ["+12345678901", "+441234567890"]}' | python3 uid2_request.py https://prod.uidapi.com/v2/identity/map [Your-Client-API-Key] [Your-Client-Secret] +``` + +詳細といくつかのプログラミング言語でのコードの例は、[リクエストの暗号化とレスポンスの復号化](../getting-started/gs-encryption-decryption.md) を参照してください。 + +## Decrypted JSON Response Format + +:::note +レスポンスは、HTTP ステータスコードが 200 の場合のみ暗号化されます。それ以外の場合、レスポンスは暗号化されません。 +::: + +復号化に成功すると、指定したメールアドレス、電話番号、またはそれぞれのハッシュに対する raw UID2 とソルトバケット ID が返されます。 + +```json +{ + "body": { + "mapped": [ + { + "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", + "bucket_id": "a30od4mNRd" + }, + { + "identifier": "Rx8SW4ZyKqbPypXmswDNuq0SPxStFXBTG/yvPns/2NQ=", + "advertising_id": "IbW4n6LIvtDj/8fCESlU0QG9K/fH63UdcTkJpAG8fIQ=", + "bucket_id": "ad1ANEmVZ" + } + ] + }, + "status": "success" +} +``` + +一部の識別子が無効と判断された場合、それらの識別子は "unmapped" リストとしてレスポンスに含まれる。この場合でも、レスポンスステータスは "success" となります。すべての識別子がマッピングされた場合、"unmapped"リストはレスポンスに含まれません。 + +```json +{ + "body": { + "mapped": [ + { + "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", + "bucket_id": "a30od4mNRd" + } + ], + "unmapped": [ + { + "identifier": "some@malformed@email@hash", + "reason": "invalid identifier" + } + ] + }, + "status": "success" +} +``` + +一部の識別子が UID2 エコシステムからオプトアウトしている場合、オプトアウトした識別子は、見つかった無効な識別子とともに "unmapped" リストに移動されます。この場合でも、レスポンスステータスは "success" です。 + +```json +{ + "body": { + "mapped": [ + { + "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", + "bucket_id": "a30od4mNRd" + } + ], + "unmapped": [ + { + "identifier": "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=", + "reason": "optout" + } + ] + }, + "status": "success" +} +``` + +### Response Body Properties + +レスポンスボディには、次の表に示すプロパティが含まれます。 + +| Property | Data Type | Description | +| :--- | :--- | :--- | +| `identifier` | string | リクエストボディで指定されたメールアドレス、電話番号、またはそれぞれのハッシュです。 | +| `advertising_id` | string | 対応する Advertising ID (raw UID2) です。 | +| `bucket_id` | string | raw UID2 の生成に使用したソルトバケットの ID です。 | + +### Response Status Codes + +次の表は、`status` プロパティの値と、それに対応する HTTP ステータスコードの一覧です。 + +| Status | HTTP Status Code | Description | +| :--- | :--- | :--- | +| `success` | 200 | リクエストは成功しました。レスポンスは暗号化されています。 | +| `client_error` | 400 | リクエストに不足している、または無効なパラメータがありました。 | +| `unauthorized` | 401 | クエストにベアラートークンが含まれていない、無効なベアラートークンが含まれている、またはリクエストされた操作を実行するのに許可されていないベアラートークンが含まれていた。 | + +`status` の値が `success` 以外であれば、`message` フィールドにその問題に関する追加情報が表示されます。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/post-identity-map.md b/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/post-identity-map.md index 3624bb8d2..5fe824ae8 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/post-identity-map.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/post-identity-map.md @@ -8,193 +8,302 @@ displayed_sidebar: docs import Link from '@docusaurus/Link'; +:::note +このページには、近日中に翻訳される新しいコンテンツが含まれています。 +::: + # POST /identity/map -複数のメールアドレス、電話番号、またはそれぞれのハッシュを、raw UID2 と salt bucket IDs にマッピングします。このエンドポイントを使用して、オプトアウト情報の更新をチェックすることもできます +Maps multiple email addresses, phone numbers, or their respective hashes to their raw UID2s. You can also use this endpoint to check for updates to opt-out information, check when a raw UID2 can be refreshed, or view the previous UID2 if the current UID2 is less than 90 days old. + +Used by: This endpoint is used mainly by advertisers and data providers. For details, see [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md). + +For details about the UID2 opt-out workflow and how users can opt out, see [User Opt-Out](../getting-started/gs-opt-out.md). -Used by: このエンドポイントは、主に広告主やデータプロバイダーが使用します。詳細は [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) を参照してください。 +## Version -UID2 の Opt-Out ワークフローとユーザーが Opt-Out する方法の詳細は、[User Opt-Out](../getting-started/gs-opt-out.md) を参照してください。 +This documentation is for the latest version of this endpoint, version 3. + +If needed, documentation is also available for the previous version: see [POST /identity/map (v2)](post-identity-map-v2.md). ## Batch Size and Request Parallelization Requirements -知っておくべきことは以下のとおりです: +Here's what you need to know: -- リクエストの最大サイズは 1MB です。 -- 大量のメールアドレス、電話番号、またはそれぞれのハッシュをマップするには、1 バッチあたり最大 5,000 アイテムのバッチサイズで、それらを *連続した* バッチで送信してください。 -- Private Operator を使用している場合を除き、バッチを並行して送信しないでください。つまり、単一の HTTP 接続を使用して、[directly identifying information (DII)](../ref-info/glossary-uid.md#gl-dii) を連続してマッピングしてください。 -- メールアドレス、電話番号、またはそれぞれのハッシュのマッピングを必ず保存してください。
    マッピングを保存しないと、数百万のメールアドレスや電話番号をマッピングする必要がある場合に、処理時間が大幅に増加する可能性があります。しかし、実際に更新が必要なマッピングのみを再計算することで、毎日更新が必要な raw UID2 の数は約 1/365 となり、総処理時間を短縮できます。[Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) と [FAQs for Advertisers and Data Providers](../getting-started/gs-faqs.md#faqs-for-advertisers-and-data-providers) も参照してください。 +- The maximum request size is 1MB. +- To map a large number of email addresses, phone numbers, or their respective hashes, send them in *sequential* batches with a maximum batch size of 5,000 items per batch. +- Unless you are using a Private Operator, do not send batches in parallel. In other words, use a single HTTP connection and send batches of hashed or unhashed directly identifying information (DII) values consecutively, without creating multiple parallel connections. +- Be sure to store mappings of email addresses, phone numbers, or their respective hashes.
    Not storing mappings could increase processing time drastically when you have to map millions of email addresses or phone numbers. Recalculating only those mappings that actually need to be updated, however, reduces the total processing time because only about 1/365th of UID2s need to be updated daily. See also [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md) and [FAQs for Advertisers and Data Providers](../getting-started/gs-faqs.md#faqs-for-advertisers-and-data-providers). ## Request Format -`POST '{environment}/v2/identity/map'` +`POST '{environment}/v3/identity/map'` -認証の詳細は、 [Authentication and Authorization](../getting-started/gs-auth.md) を参照してください。 +For authentication details, see [Authentication and Authorization](../getting-started/gs-auth.md). :::important -すべてのリクエストを秘密鍵で暗号化する必要があります。詳細といくつかのプログラミング言語でのコードの例は、[リクエストの暗号化とレスポンスの復号化](../getting-started/gs-encryption-decryption.md) を参照してください。 +You must encrypt all requests using your secret. For details, and code examples in different programming languages, see [Encrypting Requests and Decrypting Responses](../getting-started/gs-encryption-decryption.md). ::: ### Path Parameters | Path Parameter | Data Type | Attribute | Description | | :--- | :--- | :--- | :--- | -| `{environment}` | string | 必須 | テスト (インテグレーション) 環境: `https://operator-integ.uidapi.com`
    本番環境: `https://prod.uidapi.com`
    リージョンごとのオペレーターを含む全リストは [Environments](../getting-started/gs-environments.md) を参照してください。 | +| `{environment}` | string | Required | Testing (integration) environment: `https://operator-integ.uidapi.com`
    Production environment: The best choice depends on where your users are based. For information about how to choose the best URL for your use case, and a full list of valid base URLs, see [Environments](../getting-started/gs-environments.md). | :::note -インテグレーション環境と本番環境では、異なる API Key が必要です。各環境の認証情報を取得する方法については、[Getting Your Credentials](../getting-started/gs-credentials.md#getting-your-credentials) を参照してください。 +The integration environment and the production environment require different API keys. For information about getting credentials for each environment, see [Getting Your Credentials](../getting-started/gs-credentials.md#getting-your-credentials). ::: ### Unencrypted JSON Body Parameters :::important -リクエストを暗号化するときは、以下の 4 つの条件パラメータのうち、**1つ** だけをリクエストの JSON ボディにキーと値のペアとして含める必要がります。 +Include one or more of the following four parameters as key-value pairs in the JSON body of the request when encrypting it. ::: -| Body Parameter | Data Type | Attribute | Description | -| :--- | :--- | :--- | :--- | -| `email` | string array | 条件付きで必要 | マッピングするメールアドレスのリストです。 | -| `email_hash` | string array | 条件付きで必要 | マッピングする [SHA-256 ハッシュし、Base64 エンコード](../getting-started/gs-normalization-encoding.md#email-address-normalization) した [正規化](../getting-started/gs-normalization-encoding.md#email-address-hash-encoding) 済みメールアドレスのリストです。 | -| `phone` | string array | 条件付きで必要 | マッピングする [正規化](../getting-started/gs-normalization-encoding.md#phone-number-normalization) 済み電話番号のリストです。 | -| `phone_hash` | string array | 条件付きで必要 | マッピングする [SHA-256 ハッシュし、Base64 エンコード](../getting-started/gs-normalization-encoding.md#phone-number-hash-encoding) した [正規化](../getting-started/gs-normalization-encoding.md#phone-number-normalization) 済み電話番号のリストです。 | +| Body Parameter | Data Type | Attribute | Description | +|:---------------|:----------------------------|:-----------------------| :--- | +| `email` | string array | Conditionally Required | The list of email addresses to be mapped. | +| `email_hash` | string array | Conditionally Required | The list of [Base64-encoded SHA-256](../getting-started/gs-normalization-encoding.md#email-address-hash-encoding) hashes of [normalized](../getting-started/gs-normalization-encoding.md#email-address-normalization) email addresses to be mapped. | +| `phone` | string array | Conditionally Required | The list of [normalized](../getting-started/gs-normalization-encoding.md#phone-number-normalization) phone numbers to be mapped. | +| `phone_hash` | string array | Conditionally Required | The list of [Base64-encoded SHA-256](../getting-started/gs-normalization-encoding.md#phone-number-hash-encoding) hashes of [normalized](../getting-started/gs-normalization-encoding.md#phone-number-normalization) phone numbers to be mapped. | + ### Request Examples -以下は、各パラメータの暗号化されていない JSON リクエストボディの例です。このうちの 1 つを、`POST /identity/map` エンドポイントへのリクエストに含める必要があります: +The following are unencrypted JSON request body examples to the `POST /identity/map` endpoint: ```json { - "email": [ - "user@example.com", - "user2@example.com" - ] -} -``` -```json -{ - "email_hash": [ - "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=", - "KzsrnOhCq4tqbGFMsflgS7ig1QLRr0nFJrcrEIlOlbU=" - ] -} -``` -```json -{ - "phone": [ + "email":[ + "user@example.com", + "user2@example.com" + ], + "phone":[ "+12345678901", "+441234567890" - ] + ] } ``` + ```json { - "phone_hash": [ + "email_hash":[ + "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=", + "KzsrnOhCq4tqbGFMsflgS7ig1QLRr0nFJrcrEIlOlbU=" + ], + "phone_hash":[ "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", "Rx8SW4ZyKqbPypXmswDNuq0SPxStFXBTG/yvPns/2NQ=" - ] + ] } ``` -以下は、電話番号に対する `POST /identity/map` エンドポイントへの暗号化リクエストの例です: +Here's an encrypted request example to the `POST /identity/map` endpoint for phone numbers: ```sh -echo '{"phone": ["+12345678901", "+441234567890"]}' | python3 uid2_request.py https://prod.uidapi.com/v2/identity/map [Your-Client-API-Key] [Your-Client-Secret] +echo '{"phone": ["+12345678901", "+441234567890"]}' | python3 uid2_request.py https://prod.uidapi.com/v3/identity/map [YOUR_CLIENT_API_KEY] [YOUR_CLIENT_SECRET] ``` -詳細といくつかのプログラミング言語でのコードの例は、[リクエストの暗号化とレスポンスの復号化](../getting-started/gs-encryption-decryption.md) を参照してください。 +For details, and code examples in different programming languages, see [Encrypting Requests and Decrypting Responses](../getting-started/gs-encryption-decryption.md). ## Decrypted JSON Response Format :::note -レスポンスは、HTTP ステータスコードが 200 の場合のみ暗号化されます。それ以外の場合、レスポンスは暗号化されません。 +The response is encrypted only if the HTTP status code is 200. Otherwise, the response is not encrypted. ::: -復号化に成功すると、指定したメールアドレス、電話番号、またはそれぞれのハッシュに対する raw UID2 とソルトバケット ID が返されます。 +A successful decrypted response returns the current raw UID2s, previous raw UID2s, and refresh timestamps for the specified email addresses, phone numbers, or their respective hashes. -```json -{ - "body": { - "mapped": [ - { - "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", - "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", - "bucket_id": "a30od4mNRd" - }, - { - "identifier": "Rx8SW4ZyKqbPypXmswDNuq0SPxStFXBTG/yvPns/2NQ=", - "advertising_id": "IbW4n6LIvtDj/8fCESlU0QG9K/fH63UdcTkJpAG8fIQ=", - "bucket_id": "ad1ANEmVZ" - } - ] - }, - "status": "success" -} -``` +The response arrays preserve the order of input arrays. Each element in the response array maps directly to the element at the same index in the corresponding request array. This ensures that you can reliably associate results with their corresponding inputs based on array position. + +Input values that cannot be mapped to a raw UID2 are mapped to an error object with the reason for unsuccessful mapping. An unsuccessful mapping occurs if the DII is invalid or has been opted out from the UID2 ecosystem. In these cases, the response status is `success` but no raw UID2 is returned. + +The following example shows the input and corresponding response. -一部の識別子が無効と判断された場合、それらの識別子は "unmapped" リストとしてレスポンスに含まれる。この場合でも、レスポンスステータスは "success" となります。すべての識別子がマッピングされた場合、"unmapped"リストはレスポンスに含まれません。 +Input: ```json { - "body": { - "mapped": [ - { - "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", - "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", - "bucket_id": "a30od4mNRd" - } - ], - "unmapped": [ - { - "identifier": "some@malformed@email@hash", - "reason": "invalid identifier" - } + "email": [ + "user@example.com", // Corresponding UID2 rotated in the last 90 days + "user2@example.com", // Corresponding UID2 rotated more than 90 days ago + "invalid email string", // Invalid identifier + "optout@example.com" // DII is opted out ] - }, - "status": "success" } ``` -一部の識別子が UID2 エコシステムからオプトアウトしている場合、オプトアウトした識別子は、見つかった無効な識別子とともに "unmapped" リストに移動されます。この場合でも、レスポンスステータスは "success" です。 +Response: ```json { - "body": { - "mapped": [ - { - "identifier": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", - "advertising_id": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", - "bucket_id": "a30od4mNRd" - } - ], - "unmapped": [ - { - "identifier": "tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=", - "reason": "optout" - } - ] - }, - "status": "success" + "body":{ + "email": [ + { + "u": "AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=", + "p": "EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=", + "r": 1735689600000 + }, + { + "u": "IbW4n6LIvtDj/8fCESlU0QG9K/fH63UdcTkJpAG8fIQ=", + "p": null, + "r": 1735862400000 + }, + { "e": "invalid identifier" }, + { "e": "optout" } + ], + "email_hash": [], + "phone": [], + "phone_hash": [] + }, + "status": "success" } ``` ### Response Body Properties -レスポンスボディには、次の表に示すプロパティが含まれます。 +The response body includes one or more of the properties shown in the following table. -| Property | Data Type | Description | -| :--- | :--- | :--- | -| `identifier` | string | リクエストボディで指定されたメールアドレス、電話番号、またはそれぞれのハッシュです。 | -| `advertising_id` | string | 対応する Advertising ID (raw UID2) です。 | -| `bucket_id` | string | raw UID2 の生成に使用したソルトバケットの ID です。 | +| Body Parameter | Data Type | Description | +|:---------------|:----------------------------|:------------------------------------------------------------------------------------------------| +| `email` | array of mapped DII objects | The list of mapped DII objects corresponding to the list of emails in the request. | +| `email_hash` | array of mapped DII objects | The list of mapped DII objects corresponding to the list of email hashes in the request. | +| `phone` | array of mapped DII objects | The list of mapped DII objects corresponding to the list of phone numbers in the request. | +| `phone_hash` | array of mapped DII objects | The list of mapped DII objects corresponding to the list of phone number hashes in the request. | + + +For successfully mapped DII, the mapped object includes the properties shown in the following table. + +| Property | Data Type | Description | +|:---------|:-----------|:--------------------------------------------------------------------------------------------------------------------------------------| +| `u` | string | The raw UID2 corresponding to the email or phone number provided in the request. | +| `p` | string | One of the following:
    • If the current raw UID2 has been rotated in the last 90 days: the previous value.
    • If the current raw UID2 is older than 90 days: `null`.
    | +| `r` | number | The Unix timestamp (in milliseconds) that indicates when the raw UID2 might be refreshed. The raw UID2 is guaranteed to be valid until this timestamp. | + +For unsuccessfully mapped input values, the mapped object includes the properties shown in the following table. + +| Property | Data Type | Description | +|:---------|:----------|:-----------------------------------------------------------------------------------------------------------------| +| `e` | string | The reason for being unable to map the DII to a raw UID2. One of two possible values:
    • `optout`
    • `invalid identifier`
    | ### Response Status Codes -次の表は、`status` プロパティの値と、それに対応する HTTP ステータスコードの一覧です。 +The following table lists the `status` property values and their HTTP status code equivalents. | Status | HTTP Status Code | Description | | :--- | :--- | :--- | -| `success` | 200 | リクエストは成功しました。レスポンスは暗号化されています。 | -| `client_error` | 400 | リクエストに不足している、または無効なパラメータがありました。 | -| `unauthorized` | 401 | クエストにベアラートークンが含まれていない、無効なベアラートークンが含まれている、またはリクエストされた操作を実行するのに許可されていないベアラートークンが含まれていた。 | +| `success` | 200 | The request was successful. The response will be encrypted. | +| `client_error` | 400 | The request had missing or invalid parameters. | +| `unauthorized` | 401 | The request did not include a bearer token, included an invalid bearer token, or included a bearer token unauthorized to perform the requested operation. | + +If the `status` value is anything other than `success`, the `message` field provides additional information about the issue. + +## Migration from v2 Identity Map + +The following sections provide general information and guidance for migrating to version 3 from earlier versions, including: + +- [Version 3 Improvements](#version-3-improvements) +- [Key Differences Between v2 and v3](#key-differences-between-v2-and-v3) +- [Required Changes](#required-changes) +- [Additional Resources](#additional-resources) + +### Version 3 Improvements + +The v3 Identity Map API provides the following improvements over v2: + +- **Simplified Refresh Management**: You can monitor for UID2s reaching `refresh_from` timestamps instead of polling salt buckets for rotation. +- **Previous UID2 Access**: You have access to previous raw UID2s for 90 days after rotation for campaign measurement. +- **Single Endpoint**: You use only one endpoint, `/v3/identity/map`, instead of both `/v2/identity/map` and `/v2/identity/buckets`. +- **Multiple Identity Types in One Request**: You can process both emails and phone numbers in a single request. +- **Improved Performance**: The updated version uses significantly less bandwidth to process the same amount of DII. + +### Key Differences Between v2 and v3 + +The following table shows key differences between the versions. + +| Feature | V2 Implementation | V3 Implementation | +|:-------------------------------|:--------------------------------------------|:-------------------------------------------| +| Endpoints Required | `/v2/identity/map` + `/v2/identity/buckets` | `/v3/identity/map` only | +| Identity Types per Request | Single identity type only | Multiple identity types | +| Refresh Management | Monitor salt bucket rotations via `/identity/buckets` endpoint | Re-map when past `refresh_from` timestamps | +| Previous UID2 Access | Not available | Available for 90 days | + +### Required Changes + +To upgrade from an earlier version to version 3, follow these steps: + +1. [Update Endpoint URL](#1-update-endpoint-url) +2. [Update V3 Response Parsing Logic](#2-update-v3-response-parsing-logic) +3. [Replace Salt Bucket Monitoring with Refresh Timestamp Logic](#3-replace-salt-bucket-monitoring-with-refresh-timestamp-logic) + +#### 1. Update Endpoint URL + +Update any reference to the endpoint URL so that it references the /v3/ implementation, as shown in the following example. + +```python +# Before (v2) +url = '/v2/identity/map' + +# After (v3) +url = '/v3/identity/map' +``` + +#### 2. Update v3 Response Parsing Logic + +Update the logic for parsing the response, as shown in the following example. + +V2 Response Parsing: +```python +# v2: Process mapped/unmapped objects with identifier lookup +for item in response['body']['mapped']: + raw_uid = item['advertising_id'] + bucket_id = item['bucket_id'] + original_identifier = item['identifier'] + # Store mapping using identifier as key + store_mapping(original_identifier, raw_uid, bucket_id) +``` + +V3 Response Parsing: +```python +# v3: Process array-indexed responses +for index, item in enumerate(response['body']['email']): + original_email = request_emails[index] # Use array index to correlate + if 'u' in item: + # Successfully mapped + current_uid = item['u'] + previous_uid = item.get('p') # Available for 90 days after rotation, otherwise None + refresh_from = item['r'] + store_mapping(original_email, current_uid, previous_uid, refresh_from) + elif 'e' in item: + # Handle unmapped with reason + handle_unmapped(original_email, item['e']) +``` + +#### 3. Replace Salt Bucket Monitoring with Refresh Timestamp Logic + +Update your code for salt bucket monitoring, replacing it with code that checks the `refresh_from` timestamp to determine raw UID2s that are due for refresh. + +The following example shows an implementation of the v3 approach for checking refresh timestamps: + +```python +import time + +def is_refresh_needed(mapping): + now = int(time.time() * 1000) # Convert to milliseconds + return now >= mapping['refresh_from'] + +# Check individual mappings for refresh needs +to_remap = [mapping for mapping in mappings if is_refresh_needed(mapping)] +remap_identities(to_remap) +``` + +### Additional Resources +- [SDK for Java](../sdks/sdk-ref-java.md) for Java implementations (see Usage for Advertisers/Data Providers section) + + + + -`status` の値が `success` 以外であれば、`message` フィールドにその問題に関する追加情報が表示されます。 +For general information about identity mapping, see [Advertiser/Data Provider Integration Overview](../guides/integration-advertiser-dataprovider-overview.md). diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/summary-endpoints.md b/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/summary-endpoints.md index 19fe3a91e..ed0a6ed96 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/summary-endpoints.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/endpoints/summary-endpoints.md @@ -29,7 +29,7 @@ import Link from '@docusaurus/Link'; | Endpoint | Description | Request Encryption | Response Decryption | | :--- | :--- | :--- | :--- | | [POST /identity/buckets](post-identity-buckets.md) | 最後に更新されたタイムスタンプを使用して、ローテーションされたソルトバケットを監視します。 | 必須 | 必須 | -| [POST /identity/map](post-identity-map.md) | 1 つ以上のメールアドレス、電話番号、またはそれぞれのハッシュの UID2 とソルトバケット ID を取得します。 | 必須 | 必須 | +| [POST /identity/map (v2)](post-identity-map-v2.md) | 1 つ以上のメールアドレス、電話番号、またはそれぞれのハッシュの UID2 とソルトバケット ID を取得します。 | 必須 | 必須 | ## Opt-Out Status diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-credentials.md b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-credentials.md index ef8f8ebcb..f0d105248 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-credentials.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-credentials.md @@ -23,7 +23,7 @@ UID2 の 環境 と本番環境の両方を使用している場合、それぞれの環境用に別々の認証情報が提供されます。詳細は [Getting Your Credentials](#getting-your-credentials) を参照してください。 さらに、いくつかのケースでは、異なるシナリオに対して異なるセットの認証情報を持つことを勧めますが、必須ではありません。たとえば: -- UID2 Token を生成する Publisher である場合([POST /token/generate](../endpoints/post-token-generate.md) または他の方法で)、または自分のために UID2 を生成/マッピングする場合([POST /identity/map](../endpoints/post-identity-map.md) を参照)、それぞれの活動に対して異なる認証情報を持つことがあります。 +- UID2 Token を生成する Publisher である場合([POST /token/generate](../endpoints/post-token-generate.md) または他の方法で)、または自分のために UID2 を生成/マッピングする場合([POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) を参照)、それぞれの活動に対して異なる認証情報を持つことがあります。 - 広告主の場合、広告主キーを使用して複数のサービスプロバイダが運用するシナリオで、各サービスプロバイダに対して異なる認証情報割り当てることができます。 ## Getting Your Credentials diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-faqs.md b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-faqs.md index 693b7a729..5e8b680d3 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-faqs.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-faqs.md @@ -50,7 +50,7 @@ UID2 に DII を送信すると、UID2 はその情報を保存しますか? いいえ。UID2 service のコンポーネントは、DII を保存しません。 -さらに、ほとんどの場合、UID2 は、[POST /token/generate](../endpoints/post-token-generate.md)、[POST /token/refresh](../endpoints/post-token-refresh.md)、または [POST /identity/map](../endpoints/post-identity-map.md) の呼び出しが完了すると、値を全く保存しません。必要な例外は、ユーザーがオプトアウトした場合です。この場合、UID2 は、オプトアウトしたユーザーを示すハッシュ化された不透明な値を保存します。保存された値は、DII から生成された同じ UID2 に関する将来のリクエストを識別するために使用され、そのため拒否されます。 +さらに、ほとんどの場合、UID2 は、[POST /token/generate](../endpoints/post-token-generate.md)、[POST /token/refresh](../endpoints/post-token-refresh.md)、または [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) の呼び出しが完了すると、値を全く保存しません。必要な例外は、ユーザーがオプトアウトした場合です。この場合、UID2 は、オプトアウトしたユーザーを示すハッシュ化された不透明な値を保存します。保存された値は、DII から生成された同じ UID2 に関する将来のリクエストを識別するために使用され、そのため拒否されます。 #### Does UID2 allow the processing of HIPAA-regulated data? UID2 は HIPAA で規制されているデータの処理を許可しますか? @@ -182,7 +182,7 @@ UID2 フレームワークを使用する広告主やデータプロバイダー - [ソルトバケットのローテーションによって UID2 をリフレッシュするタイミングを知るには?](#how-do-i-know-when-to-refresh-the-uid2-due-to-salt-bucket-rotation) - [更新されたメールアドレスは、以前関連付けられていたバケットと同じバケットに割り当てられますか?](#do-refreshed-emails-get-assigned-to-the-same-bucket-with-which-they-were-previously-associated) -- [インクリメンタルアップデートの場合、UID2 はどのくらいの頻度で更新するべきですか?](#how-often-should-uid2s-be-refreshed-for-incremental-updates) +- [インクリメンタルアップデートの場合、UID2 はどのくらいの頻度で更新するべきですか?](#how-often-should-raw-uid2s-be-refreshed-for-incremental-updates) - [マッピング用の DII の SHA-256 はどのように生成すればよいですか?](#how-should-i-generate-the-sha-256-of-dii-for-mapping) - [メールアドレス、電話番号、または対応するハッシュと raw UID2 のマッピングを、自身のデータセットに保存すべきでしょうか?](#should-i-store-mapping-of-email-addresses-phone-numbers-or-corresponding-hashes-to-raw-uid2s-in-my-own-datasets) - [ユーザーのオプトアウトはどのように処理すればよいですか?](#how-should-i-handle-user-opt-outs) @@ -207,7 +207,7 @@ UID2 生成リクエストで提供されるメタデータには、UID2 の生 メールアドレスのマッピングや再マッピングを行う際には、バケットの数やローテーションする日、メールアドレスが割り当てられる特定のバケットについて、いかなる仮定も行わないようにしてください。 ::: -#### How often should UID2s be refreshed for incremental updates? +#### How often should raw UID2s be refreshed for incremental updates? インクリメンタルアップデートの場合、UID2 はどのくらいの頻度で更新するべきですか? オーディエンスの更新は、毎日行うことが推奨されています。 @@ -231,7 +231,7 @@ UID2 生成リクエストで提供されるメタデータには、UID2 の生 #### How should I handle user opt-outs? ユーザーのオプトアウトはどのように処理すればよいですか? -ユーザーが [Transparency and Control Portal](https://www.transparentadvertising.com/) を通じて UID2 ベースのターゲティング広告をオプトアウトすると、オプトアウト信号が DSP とパブリッシャーに送信され、DSP とパブリッシャーが入札時にオプトアウトを処理します。広告主やデータプロバイダーは、[POST /identity/map](../endpoints/post-identity-map.md) エンドポイントを通じて、ユーザーがオプトアウトしたかどうかを定期的に確認することを勧めます。 +ユーザーが [Transparency and Control Portal](https://www.transparentadvertising.com/) を通じて UID2 ベースのターゲティング広告をオプトアウトすると、オプトアウト信号が DSP とパブリッシャーに送信され、DSP とパブリッシャーが入札時にオプトアウトを処理します。広告主やデータプロバイダーは、[POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントを通じて、ユーザーがオプトアウトしたかどうかを定期的に確認することを勧めます。 広告主やデータプロバイダーは、raw UID2 に対するオプトアウトステータスを確認するために、[POST /optout/status](../endpoints/post-optout-status.md) エンドポイントを使用することもできます。 @@ -240,16 +240,16 @@ UID2 生成リクエストで提供されるメタデータには、UID2 の生 #### Does the same DII always result in the same raw UID2? 同じ DII は常に同じ raw UID2 になりますか? -一般的にその通りです。DII から raw UID2 を生成するプロセスは同じであり、誰がリクエストを送信したかに関係なく、結果は同じ値になります。 2 人の UID2 参加者が同じメールアドレスを [POST /identity/map](../endpoints/post-identity-map.md) エンドポイントに同時に送信した場合、応答として両方とも同じ raw UID2 を取得します。 +一般的にその通りです。DII から raw UID2 を生成するプロセスは同じであり、誰がリクエストを送信したかに関係なく、結果は同じ値になります。 2 人の UID2 参加者が同じメールアドレスを [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントに同時に送信した場合、応答として両方とも同じ raw UID2 を取得します。 -ただし、raw UID2 の生成に使用される秘密の [ソルト](../ref-info/glossary-uid.md#gl-salt) 値という可変要素があります。ソルト値は定期的にローテーションされます(詳細は [How often should UID2s be refreshed for incremental updates?](#how-often-should-uid2s-be-refreshed-for-incremental-updates)) を参照)。あるリクエストと別のリクエストの間でソルト値が変化する場合、DII が同じであっても、これら 2 つのリクエストは 2 つの異なる raw UID2 になります。 +ただし、raw UID2 の生成に使用される秘密の [ソルト](../ref-info/glossary-uid.md#gl-salt) 値という可変要素があります。ソルト値は定期的にローテーションされます(詳細は [How often should raw UID2s be refreshed for incremental updates?](#how-often-should-raw-uid2s-be-refreshed-for-incremental-updates)) を参照)。あるリクエストと別のリクエストの間でソルト値が変化する場合、DII が同じであっても、これら 2 つのリクエストは 2 つの異なる raw UID2 になります。 詳細は、*Advertiser/Data Provider Integration Guide*の [Monitor for Salt Bucket Rotations for Your Stored Raw UID2s](../guides/integration-advertiser-dataprovider-overview.md#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s) を参照してください。 #### If two operators process the same DII, are the results the same? 2 つの Operator が同じ DII を処理した場合、結果は同じになりますか? -はい、リクエストが raw UID2 に対するものである場合は、同じです。前の FAQ で説明したように、[同じ DII は常に同じ raw UID2 になりますか?](#does-the-same-dii-always-result-in-the-same-raw-uid2)、広告主やデータプロバイダーが同時に同じ DII を UID2 Operator に送信する場合、SDK または [POST /identity/map](../endpoints/post-identity-map.md) エンドポイントを使用して、同じ raw UID2 が生成されます。 +はい、リクエストが raw UID2 に対するものである場合は、同じです。前の FAQ で説明したように、[同じ DII は常に同じ raw UID2 になりますか?](#does-the-same-dii-always-result-in-the-same-raw-uid2)、広告主やデータプロバイダーが同時に同じ DII を UID2 Operator に送信する場合、SDK または [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントを使用して、同じ raw UID2 が生成されます。 Operator に関係なく、また、Private Operator と Public Operator のどちらであっても、結果は同じです。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-opt-out.md b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-opt-out.md index 399d719a8..e87fbf7bf 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-opt-out.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-opt-out.md @@ -50,7 +50,7 @@ UID2 エコシステムには、2 種類のオプトアウトがあります: | :--- | :--- | | Publishers | [POST /token/generate](../endpoints/post-token-generate.md) を必須パラメータ `optout_check` を `1` に設定して呼び出したパブリッシャー、または [POST /token/refresh](../endpoints/post-token-refresh.md) を呼び出したパブリッシャーは、UID2 Token の代わりにオプトアウトレスポンスを受け取ります。| | DSPs | UID2 Operator Service は、DSP に対して、その目的のために提供された Webhook を介して、オプトアウトしたすべてのユーザーの情報を配布します。詳細は [Honor User Opt-Outs](../guides/dsp-guide#honor-user-opt-outs) を参照してください。
    DSP は、[POST /optout/status](../endpoints/post-optout-status.md) エンドポイントを使用して、raw UID2 のオプトアウトステータスを確認することもできます。 | - | 広告主とデータプロバイダー | UID2 Operator Service は、[POST /identity/map](../endpoints/post-identity-map.md) エンドポイントを介して、広告主とデータプロバイダーにオプトアウト情報を配布します。別のオプションとして、[POST /optout/status](../endpoints/post-optout-status.md) エンドポイントを使用して、raw UID2 のオプトアウトステータスを確認することもできます。 | + | 広告主とデータプロバイダー | UID2 Operator Service は、[POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントを介して、広告主とデータプロバイダーにオプトアウト情報を配布します。別のオプションとして、[POST /optout/status](../endpoints/post-optout-status.md) エンドポイントを使用して、raw UID2 のオプトアウトステータスを確認することもできます。 | | Sharers | UID2 Sharer は、[POST /optout/status](../endpoints/post-optout-status.md) エンドポイントを使用して、raw UID2 のオプトアウトステータスを確認することができます。 | このワークフローにより、ユーザーは Transparency and Control Portal を通じて、UID2 に基づくパーソナライズ広告をオプトアウトすることができます。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-permissions.md b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-permissions.md index 7d7d147d0..3f52f2c1a 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-permissions.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/getting-started/gs-permissions.md @@ -27,4 +27,4 @@ UID2 エコシステムには、特定のアクティビティを完了するた | Generator | Publishers | Permission to call the [POST /token/generate](../endpoints/post-token-generate.md), [POST /token/validate](../endpoints/post-token-validate.md), and [POST /token/refresh](../endpoints/post-token-refresh.md) の各エンドポイントを呼び出して、DII から UID2 Token を生成/リフレッシュする権限:
    • Prebid インテグレーション
    • The SDK for JavaScript
    • UID2 Token の取得と管理のために、該当する API エンドポイントを直接呼び出すインテグレーション
    | | Bidder | DSPs | パブリッシャーからのビッドストリームから送られてくる UID2 Token を、入札目的で raw UID2 に復号化する権限。 | | Sharer | UID2 sharing に参加するすべての参加者タイプ。詳細は [UID2 Sharing: Overview](../sharing/sharing-overview.md) を参照してください。 | 以下両方の権限:
    • UID2 SDK または S nowflake を使用して、raw UID2 を UID2 Token に暗号化し、許可された別の共有参加者と共有します。
    • 他の許可された共有参加者から受け取った UID2 Token を raw UID2 に復号します。
    | -| Mapper | Advertisers
    Data Providers | [POST /identity/buckets](../endpoints/post-identity-buckets.md) エンドポイントを使用して、ローテーションされたソルトバケットをモニターし、[POST /identity/map](../endpoints/post-identity-map.md) エンドポイントを使用して、複数のメールアドレス、電話番号、またはそれぞれのハッシュを、raw UID2 とソルトバケット ID にマッピングする権限。 | +| Mapper | Advertisers
    Data Providers | [POST /identity/buckets](../endpoints/post-identity-buckets.md) エンドポイントを使用して、ローテーションされたソルトバケットをモニターし、[POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントを使用して、複数のメールアドレス、電話番号、またはそれぞれのハッシュを、raw UID2 とソルトバケット ID にマッピングする権限。 | diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-endpoints.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-endpoints.md index e6c96ebff..463f1f8cc 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-endpoints.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-endpoints.md @@ -62,8 +62,8 @@ DII は、ユーザーの正規化されたメールアドレスまたは電話 | Step | Endpoint | Description | | --- | --- | --- | -| 1-a | [POST /identity/map](../endpoints/post-identity-map.md) request | DII を含むリクエストを ID マッピングエンドポイントに送信します。 | -| 1-b | [POST /identity/map](../endpoints/post-identity-map.md) response | レスポンスで返される `advertising_id` (raw UID2) は、関連する DSP でオーディエンスをターゲットするために使用できます。
    レスポンスは、ユーザーの raw UID2 と、それに対応するソルトバケットの `bucket_id` を返します。バケットに割り当てられたソルトは年に一度ローテーションし、生成された raw UID2 に影響を与えます。ソルトバケットのローテーションを確認する方法の詳細は、[5: Monitor for salt bucket rotations related to your stored raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s) を参照してください。 | +| 1-a | [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) request | DII を含むリクエストを ID マッピングエンドポイントに送信します。 | +| 1-b | [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) response | レスポンスで返される `advertising_id` (raw UID2) は、関連する DSP でオーディエンスをターゲットするために使用できます。
    レスポンスは、ユーザーの raw UID2 と、それに対応するソルトバケットの `bucket_id` を返します。バケットに割り当てられたソルトは年に一度ローテーションし、生成された raw UID2 に影響を与えます。ソルトバケットのローテーションを確認する方法の詳細は、[5: Monitor for salt bucket rotations related to your stored raw UID2s](#5-monitor-for-salt-bucket-rotations-for-your-stored-raw-uid2s) を参照してください。 | ### 2: Store Raw UID2s and Salt Bucket IDs @@ -105,8 +105,8 @@ raw UID2 は、特定の時点におけるユーザーの識別子です。raw U | --- | --- | --- | | 5-a | [POST /identity/buckets](../endpoints/post-identity-buckets.md) | 特定のタイムスタンプ以降に変更されたすべてのソルトバケットに対して、`POST /identity/buckets` エンドポイントにリクエストを送信します。 | | 5-b | [POST /identity/buckets](../endpoints/post-identity-buckets.md) | UID2 Service: `POST /identity/buckets` エンドポイントは、`bucket_id` と `last_updated` タイムスタンプのリストを返します。 | -| 5-c | [POST /identity/map](../endpoints/post-identity-map.md) | 返された `bucket_id` をキャッシュした raw UID2 のソルトバケットと比較します。
    1 つ以上の raw UID2 のソルトバケットが更新された場合は、新しい raw UID2 用に DII を `POST /identity/map` エンドポイントに再送信します。 | -| 5-d | [POST /identity/map](../endpoints/post-identity-map.md) | `advertising_id` と `bucket_id` の新しい値を保存します。 | +| 5-c | [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) | 返された `bucket_id` をキャッシュした raw UID2 のソルトバケットと比較します。
    1 つ以上の raw UID2 のソルトバケットが更新された場合は、新しい raw UID2 用に DII を `POST /identity/map` エンドポイントに再送信します。 | +| 5-d | [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) | `advertising_id` と `bucket_id` の新しい値を保存します。 | #### Determine whether the salt bucket has been rotated @@ -126,7 +126,7 @@ raw UID2 は、特定の時点におけるユーザーの識別子です。raw U UID2 Operator Service に最新のオプトアウト情報があるかを確認する方法は 2 つあります: -- [POST /identity/map](../endpoints/post-identity-map.md) エンドポイントを呼び出してオプトアウトを確認します。DII がオプトアウトされている場合、raw UID2 は生成されません。 +- [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントを呼び出してオプトアウトを確認します。DII がオプトアウトされている場合、raw UID2 は生成されません。 - [POST /optout/status](../endpoints/post-optout-status.md) エンドポイントを使用して、raw UID2 のオプトアウトステータスを確認します。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-overview.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-overview.md index b4f992e2a..7f77c2694 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-overview.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/integration-advertiser-dataprovider-overview.md @@ -49,7 +49,7 @@ import Link from '@docusaurus/Link'; | High-Level Step | Implementation Options | | --- | --- | -| [1: Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii) | DII を raw UID2 にマッピングするには、以下のオプションのいずれかをします:
    • 以下の UID2 SDK のいずれか:
      • Python SDK: [DII を Raw UID2 にマッピング](../sdks/sdk-ref-python.md#map-dii-to-raw-uid2s)
      • Java SDK: [広告主/データプロバイダー向けの使用法](../sdks/sdk-ref-java.md#usage-for-advertisersdata-providers)
    • Snowflake: [DII をマッピング](integration-snowflake.md#map-dii)
    • AWS Entity Resolution: [AWS Entity Resolution インテグレーションガイド](integration-aws-entity-resolution.md)
    • HTTP エンドポイント: [POST /identity/map](../endpoints/post-identity-map.md)
    | +| [1: Generate Raw UID2s from DII](#1-generate-raw-uid2s-from-dii) | DII を raw UID2 にマッピングするには、以下のオプションのいずれかをします:
    • 以下の UID2 SDK のいずれか:
      • Python SDK: [DII を Raw UID2 にマッピング](../sdks/sdk-ref-python.md#map-dii-to-raw-uid2s)
      • Java SDK: [広告主/データプロバイダー向けの使用法](../sdks/sdk-ref-java.md#usage-for-advertisersdata-providers)
    • Snowflake: [DII をマッピング](integration-snowflake.md#map-dii)
    • AWS Entity Resolution: [AWS Entity Resolution インテグレーションガイド](integration-aws-entity-resolution.md)
    • HTTP エンドポイント: [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md)
    | | [2: Store Raw UID2s and Salt Bucket IDs](#2-store-raw-uid2s-and-salt-bucket-ids) | カスタム(適切な方法で)。 | | [3: Manipulate or Combine Raw UID2s](#3-manipulate-or-combine-raw-uid2s) | カスタム(適切な方法で)。 | | [4: Send Stored Raw UID2s to DSPs to Create Audiences or Conversions](#4-send-stored-raw-uid2s-to-dsps-to-create-audiences-or-conversions) | カスタム(適切な方法で)。 | @@ -85,7 +85,7 @@ raw UID2 を生成するには、以下のオプションのいずれかを使 - AWS Entity Resolution: [AWS Entity Resolution Integration Guide](integration-aws-entity-resolution.md) を参照してください。 -- HTTP endpoints: [POST /identity/map](../endpoints/post-identity-map.md). 詳細は、[Generate Raw UID2s from DII](integration-advertiser-dataprovider-endpoints.md#1-generate-raw-uid2s-from-dii) を参照してください。 +- HTTP endpoints: [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md). 詳細は、[Generate Raw UID2s from DII](integration-advertiser-dataprovider-endpoints.md#1-generate-raw-uid2s-from-dii) を参照してください。 ### 2: Store Raw UID2s and Salt Bucket IDs @@ -150,7 +150,7 @@ AWS Entity Resolution では、ソルトバケットの監視方法はありま UID2 Operator Service で最新のオプトアウト情報があるかを確認する方法は 2 つあります: -- [POST /identity/map](../endpoints/post-identity-map.md) エンドポイイントを使用して、raw UID2 のオプトアウトステータスを確認します。オプトアウトされた DII には、raw UID2 は生成されません。 +- [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイイントを使用して、raw UID2 のオプトアウトステータスを確認します。オプトアウトされた DII には、raw UID2 は生成されません。 - [POST /optout/status](../endpoints/post-optout-status.md) エンドポイントを使用して、raw UID2 のオプトアウトステータスを確認します。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/ref-info/updates-doc.md b/i18n/ja/docusaurus-plugin-content-docs/current/ref-info/updates-doc.md index 44cc5f60a..f8af017e1 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/ref-info/updates-doc.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/ref-info/updates-doc.md @@ -423,7 +423,7 @@ March 4, 2024 February 28, 2024 -Java SDKは、[POST /identity/map](../endpoints/post-identity-map.md) エンドポイントの使用を希望する広告主およびデータプロバイダーをサポートするようになりました。 +Java SDKは、[POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントの使用を希望する広告主およびデータプロバイダーをサポートするようになりました。 詳細は、*SDK for Javaリファレンスガイド*: [Usage for Advertisers and Data Providers](../sdks/sdk-ref-java.md#usage-for-advertisersdata-providers) の更新されたドキュメントを参照してください。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/sdks/summary-sdks.md b/i18n/ja/docusaurus-plugin-content-docs/current/sdks/summary-sdks.md index 6dbc97fed..b5de39bd0 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/sdks/summary-sdks.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/sdks/summary-sdks.md @@ -28,7 +28,7 @@ SDK の機能を確認して使用する SDK を決定し、SDK の表をクリ |Android | Client (Mobile) | — | — | ✅ | ✅ | — | — | |iOS | Client (Mobile) | — | — | ✅| ✅ |— | — | -*DII から raw UID2 を生成する必要がある広告主およびデータプロバイダは Snowflake ([Snowflake Integration Guide](../guides/integration-snowflake.md) を参照) または [POST /identity/map](../endpoints/post-identity-map.md) エンドポイントを使用することができます。 +*DII から raw UID2 を生成する必要がある広告主およびデータプロバイダは Snowflake ([Snowflake Integration Guide](../guides/integration-snowflake.md) を参照) または [POST /identity/map (v2)](../endpoints/post-identity-map-v2.md) エンドポイントを使用することができます。 diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/sharing/sharing-tokenized-overview.md b/i18n/ja/docusaurus-plugin-content-docs/current/sharing/sharing-tokenized-overview.md index 5a6c3a6fa..21259284b 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/sharing/sharing-tokenized-overview.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/sharing/sharing-tokenized-overview.md @@ -166,7 +166,7 @@ raw UID2 から始める場合は、次の手順に従ってください: 1 user@example.com -メールアドレス/電話番号を raw UID2 に変換する:
    POST /identity/map endpoint +メールアドレス/電話番号を raw UID2 に変換する:
    POST /identity/map (v2) endpoint K2jlbu2ldlpKL1z6n5bET7L3
    g0xfqmldZPDdPTktdRQ= diff --git a/package-lock.json b/package-lock.json index 7be271061..80f7893c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6055,15 +6055,9 @@ } }, "node_modules/caniuse-lite": { -<<<<<<< Updated upstream - "version": "1.0.30001722", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", - "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", -======= "version": "1.0.30001723", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", ->>>>>>> Stashed changes "funding": [ { "type": "opencollective", diff --git a/sidebars.js b/sidebars.js index 673e044fd..78afd55c5 100644 --- a/sidebars.js +++ b/sidebars.js @@ -320,7 +320,18 @@ const fullSidebar = [ 'endpoints/post-token-validate', 'endpoints/post-token-refresh', 'endpoints/post-identity-buckets', - 'endpoints/post-identity-map', + { + type: 'category', + label: 'POST /identity/map', + link: { + type: 'doc', + id: 'endpoints/post-identity-map', + }, + collapsed: true, + items: [ + 'endpoints/post-identity-map-v2', + ], + }, 'endpoints/post-optout-status', ], }, @@ -400,6 +411,7 @@ const sidebars = { 'guides/dsp-guide', 'endpoints/post-identity-buckets', 'endpoints/post-identity-map', + 'endpoints/post-identity-map-v2', 'endpoints/post-optout-status' ), diff --git a/test/python/.gitignore b/test/python/.gitignore new file mode 100644 index 000000000..1ddd7a65f --- /dev/null +++ b/test/python/.gitignore @@ -0,0 +1,107 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +.env +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# pytest +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.nox/ +.cache + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Linux +*~ + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Logs +*.log + +# Test results +test-results/ +coverage.xml +*.cover +*.py,cover +.hypothesis/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# poetry +poetry.lock + +# Celery +celerybeat-schedule +celerybeat.pid + +# SageMath +*.sage.py + +# Environments +.env +.env.local +.env.*.local + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Local test data +test_data/ +temp/ +tmp/ \ No newline at end of file diff --git a/test/python/README.md b/test/python/README.md new file mode 100644 index 000000000..24d8671f2 --- /dev/null +++ b/test/python/README.md @@ -0,0 +1,26 @@ +# UID2 Documentation Python Tests + +This directory contains Python tests for the code samples in the [POST /identity/map v3 documentation](../../docs/endpoints/post-identity-map.md). The tests verify that the Python code examples in the documentation work correctly and demonstrate proper usage patterns. + +The intention is not to test the UID2 features or clients - we assume those work based on other tests. + +## Quick Start + +### Prerequisites + +1. **Install UV** (Python package manager): + ```bash + pip install uv + ``` + +## Environment Configuration +Fill the following environment variables into the `.env` file. The file is gitignored. +- `UID2_BASE_URL` - Integration environment endpoint +- `UID2_API_KEY` - Test API key +- `UID2_SECRET_KEY` - Test secret key + +### Run Tests +```bash +cd test/python +uv run pytest +``` diff --git a/test/python/pyproject.toml b/test/python/pyproject.toml new file mode 100644 index 000000000..5937ac6ac --- /dev/null +++ b/test/python/pyproject.toml @@ -0,0 +1,30 @@ +[project] +name = "uid2-docs-python-tests" +version = "0.1.0" +description = "Python tests for UID2 documentation code samples" +requires-python = ">=3.8" +dependencies = [ + "uid2-client>=3.0.0a1", + "pytest>=7.0.0", + "python-dotenv>=1.0.0", +] + +[project.optional-dependencies] +dev = [ + "pytest-cov>=4.0.0", +] + +[tool.pytest.ini_options] +testpaths = ["."] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "-v", + "--tb=short", + "--disable-warnings", +] +filterwarnings = [ + "ignore::DeprecationWarning", + "ignore::PendingDeprecationWarning", +] \ No newline at end of file diff --git a/test/python/test/test_post_identity_map_v3.py b/test/python/test/test_post_identity_map_v3.py new file mode 100644 index 000000000..e4ed4e912 --- /dev/null +++ b/test/python/test/test_post_identity_map_v3.py @@ -0,0 +1,181 @@ +import os +import time +import pytest +from dotenv import load_dotenv +from uid2_client import IdentityMapClient, IdentityMapInput + +load_dotenv() + +""" +!!!!! Do not refactor this code if you're not intending to change the documentation samples !!!!! + +Tests for sample code as used in https://unifiedid.com/docs/endpoints/post-identity-map-v3 +The tests are designed to have sections of almost exactly copy/pasted code samples so there are +unused variables, unnecessary comments, redundant repetition... since those are used in docs for illustration. +If a test breaks in this file, likely the change breaks one of the samples on the docs site +""" + + +@pytest.fixture(scope="module") +def test_config(): + return { + "UID2_BASE_URL": os.getenv("UID2_BASE_URL"), + "UID2_API_KEY": os.getenv("UID2_API_KEY"), + "UID2_SECRET_KEY": os.getenv("UID2_SECRET_KEY"), + "mapped_email": "user@example.com", + "opted_out_email": "optout@example.com", + } + + +@pytest.fixture(scope="module") +def identity_map_client(test_config): + return IdentityMapClient( + test_config["UID2_BASE_URL"], + test_config["UID2_API_KEY"], + test_config["UID2_SECRET_KEY"] + ) + + +def test_endpoint_url_update_example(): + """ + Documentation post-identity-map.md Lines 223-229: Update Endpoint URL + """ + # Before (V2) + url = '/v2/identity/map' + + # After (V3) + url = '/v3/identity/map' + + # Verify the URL update + assert url == '/v3/identity/map' + + +def test_v2_response_parsing_example(): + """ + Documentation post-identity-map.md Lines 235-242: V2 Response Parsing + """ + # Mock V2 response structure + response = { + 'body': { + 'mapped': [ + { + 'identifier': 'EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=', + 'advertising_id': 'AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=', + 'bucket_id': 'a30od4mNRd' + } + ] + } + } + + def store_mapping(original_identifier, raw_uid, bucket_id): + # Store mapping using identifier as key + assert original_identifier is not None + assert raw_uid is not None + assert bucket_id is not None + + # V2: Process mapped/unmapped objects with identifier lookup + for item in response['body']['mapped']: + raw_uid = item['advertising_id'] + bucket_id = item['bucket_id'] + original_identifier = item['identifier'] + # Store mapping using identifier as key + store_mapping(original_identifier, raw_uid, bucket_id) + + +def test_v3_response_parsing_example(): + """ + Documentation post-identity-map.md Lines 246-258: V3 Response Parsing + """ + # Mock V3 response structure + response = { + 'body': { + 'email': [ + { + 'u': 'AdvIvSiaum0P5s3X/7X8h8sz+OhF2IG8DNbEnkWSbYM=', + 'p': 'EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=', + 'r': 1735689600000 + }, + {'e': 'invalid identifier'} + ] + } + } + + # Mock request data + request_emails = ['user@example.com', 'invalid@email'] + + def store_mapping(original_email, current_uid, previous_uid, refresh_from): + assert original_email is not None + assert current_uid is not None + assert refresh_from is not None + + def handle_unmapped(original_email, reason): + assert original_email is not None + assert reason is not None + + # V3: Process array-indexed responses + for index, item in enumerate(response['body']['email']): + original_email = request_emails[index] # Use array index to correlate + if 'u' in item: + # Successfully mapped + current_uid = item['u'] + previous_uid = item.get('p') # Available for 90 days after rotation, otherwise None + refresh_from = item['r'] + store_mapping(original_email, current_uid, previous_uid, refresh_from) + elif 'e' in item: + # Handle unmapped with reason + handle_unmapped(original_email, item['e']) + + +def test_refresh_timestamp_logic_example(): + """ + Documentation post-identity-map.md Lines 264-273: Replace Salt Bucket Monitoring with Refresh Timestamp Logic + """ + import time + + def is_refresh_needed(mapping): + now = int(time.time() * 1000) # Convert to milliseconds + return now >= mapping['refresh_from'] + + def remap_identities(to_remap): + # Mock function to remap identities + assert to_remap is not None + return True + + # Mock mappings data + current_time = int(time.time() * 1000) + mappings = [ + { + 'identifier': 'user1@example.com', + 'refresh_from': current_time + 3600000 # 1 hour in future + }, + { + 'identifier': 'user2@example.com', + 'refresh_from': current_time - 3600000 # 1 hour in past (needs refresh) + } + ] + + # Check individual mappings for refresh needs + to_remap = [mapping for mapping in mappings if is_refresh_needed(mapping)] + remap_identities(to_remap) + + # Verify refresh logic + assert len(to_remap) == 1 # Only one mapping should need refresh + assert to_remap[0]['identifier'] == 'user2@example.com' + + +def test_integration_example(identity_map_client, test_config): + """ + Integration test with actual API calls to verify the patterns work + """ + # Test with actual API client + identity_map_input = IdentityMapInput.from_emails([test_config["mapped_email"]]) + response = identity_map_client.generate_identity_map(identity_map_input) + + # Verify response structure + assert response is not None + assert hasattr(response, 'mapped_identities') + assert hasattr(response, 'unmapped_identities') + + # Verify at least some processing occurred + total_processed = len(response.mapped_identities) + len(response.unmapped_identities) + assert total_processed >= 1 \ No newline at end of file diff --git a/test/python/uv.lock b/test/python/uv.lock new file mode 100644 index 000000000..53bd57bd1 --- /dev/null +++ b/test/python/uv.lock @@ -0,0 +1,762 @@ +version = 1 +revision = 2 +requires-python = ">=3.8" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] + +[[package]] +name = "bitarray" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/36/eef91e03e44be4b96a613333acfc0c636af2f3f3033b17e08e2052b649c5/bitarray-3.4.3.tar.gz", hash = "sha256:dddfb2bf086b66aec1c0110dc46642b7161f587a6441cfe74da9e323975f62f0", size = 143930, upload-time = "2025-06-23T23:23:20.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/83/cbaaf95b2aacde284c76e53ef0b58eb328c2cd6777d728cf089276a05a55/bitarray-3.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a0c126a6ed1d3cd68cd91c0056cee8edcf6aa57c557b555528fe37375e72ea74", size = 140944, upload-time = "2025-06-23T23:19:37.318Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/04146d674af13d6c57b0d384627d739ce09429b034fe31361b03dc3eaee7/bitarray-3.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:690fc6d2b5c5e267f643e3720e8b4203838d3f30439e2070dccfae473b8223c3", size = 137539, upload-time = "2025-06-23T23:19:39.656Z" }, + { url = "https://files.pythonhosted.org/packages/29/df/98b425efeef3910ae50c4d6bb5b335c4f3134ad528229e98bcb0a75a7051/bitarray-3.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639930dadc248e68caef99f068dea70cea58244f199c4ca63975ae6292f6e921", size = 306831, upload-time = "2025-06-23T23:19:41.277Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ac/4d063978a4d1bd29bca7993da1cd2aefe44afa6a70d9e5213f4494bd4d9a/bitarray-3.4.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6646d4f5533383a54d29c212b2ece7e1988d18f1e0f9e2e814bc96d4defdb39b", size = 322782, upload-time = "2025-06-23T23:19:42.702Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b9/f0bb03d02338c5a34f0555f861d5ff03f2ec3b4b5c89455f60f7b9126980/bitarray-3.4.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:600e5805f2de242522ae598c4c43f95eda3cae63ca9ef01cdb659cb006bc3a86", size = 314984, upload-time = "2025-06-23T23:19:44.379Z" }, + { url = "https://files.pythonhosted.org/packages/03/be/55cf2dbe50b39d96956b5f642fddcb2692afb3cfdd2acaa077043a9ce38d/bitarray-3.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8815f3b7fd99d5b596e2ab29f9d14ce97c39ee9fc70fab6ba25fbb279b2b7bc", size = 308238, upload-time = "2025-06-23T23:19:46.036Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f6/c6ae7c864234f59bdd4f594e376fc979d05c07e75c5b02e9405bc22b8bc0/bitarray-3.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb3dd74f05f0640b4a7aa2490c7b04d4c597f18623b8cdeca2700bade30747d", size = 295809, upload-time = "2025-06-23T23:19:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/79/c6/8ea6f514dde20f4c7d6704909461085fc2c49406ab6070e627fd8b20eff7/bitarray-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9f8dc515136436a3682d3954f9a86d90b6f80d98b07fe03371cf933961d19c66", size = 301034, upload-time = "2025-06-23T23:19:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/28d7f620fabd9c1392e9b77cedc653e58c9b1ae5de267a1914f38a8b8e1b/bitarray-3.4.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c686b34b189160971cdfcaa31279ccdc6e90ecfb113b6c84b8761d74a308e4cf", size = 292385, upload-time = "2025-06-23T23:19:50.83Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/ae6bc649aedefa2feec811de25fa8108b4f5dd36684be0432a4db08bd06f/bitarray-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c9e7e7fa3870c8c164446b838d72ef88acb47bd3552b68f7bc509fcd13366004", size = 317744, upload-time = "2025-06-23T23:19:52.463Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4b/a7596c213293ea59bb7887fd5731b3022a797797a4e949ac30bd47648e0f/bitarray-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:79dff08c581ed3ac85eab019bd8af8ccab744651dc6f9e5acc3a44d96dd491aa", size = 320362, upload-time = "2025-06-23T23:19:54.095Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2b/e80665aafd6ae9cc8e3cb48cab3a413e9ac19f1a250a46980e060f8e8a1e/bitarray-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:30522c7cca78cb68894c3b5f114aef79b6a62113289239ca0351ae11b4ec074d", size = 300015, upload-time = "2025-06-23T23:19:55.835Z" }, + { url = "https://files.pythonhosted.org/packages/25/03/8f039fa65542c344d45d0fdf6b7fd3726befc04fe6f791542b4d070cda23/bitarray-3.4.3-cp310-cp310-win32.whl", hash = "sha256:860607c7ee614e8898c1fa6bb001a56c4af9c94d6204bcc32f4259b814fece10", size = 134426, upload-time = "2025-06-23T23:19:57.003Z" }, + { url = "https://files.pythonhosted.org/packages/4a/74/2b0897ef8772d4bba177ced4f555090b2f92dfbb6acf257e92b93021ba03/bitarray-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:65298a9aa7b16f543f94619e88b95c269de625beac3441c440e2d6b4e99cdd2c", size = 141029, upload-time = "2025-06-23T23:19:58.081Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fb/babcbe71bc7588cc0bdad72b4cb7165582e38f61cf1aee08139577bbae2c/bitarray-3.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dad06c638adb14c2ab2cdbe295f324e72c7068d65bb5612be5f170e5682a1e3e", size = 140940, upload-time = "2025-06-23T23:19:59.696Z" }, + { url = "https://files.pythonhosted.org/packages/0a/88/62296a8e4bf34d3cb87c623715de87e9de70300c60da4dbca59473fda264/bitarray-3.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0c6dc58399e2b1221f98b8696cdb414a8c42c2cea5c61f7cf9d691ee12c86cb3", size = 137545, upload-time = "2025-06-23T23:20:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/23/fd/e5885fbc65ba1a6bf6bd49f3fd90cc90889f03fe9a8a3e581531777135ee/bitarray-3.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:902b83896e0a4976186e3ec3c0064edd18dab886845644ef25c5e3c760999ed4", size = 314454, upload-time = "2025-06-23T23:20:02.627Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5a/b1511f7c3e33715a580a9f3d9ba8f45bce0ea745490fe8163bc4ae048ee8/bitarray-3.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e80131f03e4df4d9e70c355e5c939673eabaff47803fe1b85bf9676cb805e8a", size = 330842, upload-time = "2025-06-23T23:20:03.997Z" }, + { url = "https://files.pythonhosted.org/packages/86/df/fa11701e2ad8a8ffcabcfb82f9c7c78d47bc7aa1fe626bd320fc6b553e53/bitarray-3.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a544478f91a0239ce986c90af5dfbeb5ae758e4573194c94827705c138eb75b5", size = 323722, upload-time = "2025-06-23T23:20:05.846Z" }, + { url = "https://files.pythonhosted.org/packages/78/c1/fe8c84a3d3bde1eda2a222f7060278257d9a21318a27ba99fda5cfb6b801/bitarray-3.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3df507b700c5bd4f2d02056b9db1867e0a5c202fa22eb0d12a6dcca6098b1c0a", size = 316038, upload-time = "2025-06-23T23:20:07.571Z" }, + { url = "https://files.pythonhosted.org/packages/52/ea/3170ebc9c3c460b2e93f0bca19f79343e445064662203ffff5a752698227/bitarray-3.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ad9bf2403d69080bcd281fc3a4feab14fac8221362724e791df5d50aa105ea", size = 303936, upload-time = "2025-06-23T23:20:08.869Z" }, + { url = "https://files.pythonhosted.org/packages/a2/56/a6dad0cee4ce7fc11e3ec1a616f8be058afede7c4ea05db66657a0384b44/bitarray-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cda69a119698a6ab00e30bc3530d6631245312f6b2287c24b02b3bcea482f512", size = 309117, upload-time = "2025-06-23T23:20:10.202Z" }, + { url = "https://files.pythonhosted.org/packages/1b/98/17d679e3ca3eefc3346adb08432e80ea8d283fe3cfa271c8b46dff92d09a/bitarray-3.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:350357713dd175788f1e43b85998d8290b8626eb8e5dcc55571a64f8e231dcc1", size = 300071, upload-time = "2025-06-23T23:20:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/66/c1/2c49e405a5df4dd8c8bf0b4ddbe48c966a5ec8799b0a8aed7cdc860dd312/bitarray-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:17d34aab4b70d8c67260d76810d4aca65ef8bc61e829da32f9fa7116338430e3", size = 325565, upload-time = "2025-06-23T23:20:13.566Z" }, + { url = "https://files.pythonhosted.org/packages/67/42/1df9d926af530fdf8d6cd26e9e618956b79db612a0d2b79e0864de875361/bitarray-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1f266e76e2819cfdd3522247fb33caccf661c7913e0a0e29e195b46a678be660", size = 328567, upload-time = "2025-06-23T23:20:15.09Z" }, + { url = "https://files.pythonhosted.org/packages/98/a5/cccffb02a3f3d2bf59e5a5950e7939b673a3aaa7061d9218bb8fac3f840d/bitarray-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:822963d34081d2d0b0767eaf1a161ac97b03f552fa21c2c7543d9433b88694b0", size = 308143, upload-time = "2025-06-23T23:20:16.811Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2d/0b7d2f79ca3b8e67cc1afa6335567ea6d7e46d89a5ead9644af8c7fcc5b7/bitarray-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6a345df7fa08af4d08f99a555d949003ff9309d5496c469b7f3dd50c402da973", size = 134586, upload-time = "2025-06-23T23:20:18.476Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8b/48c371ad2ea678eb1b1551ecfba603d8e153b5127445b89d549e9aee479a/bitarray-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:8dc0772146d39d6819d01b64d41a9bf5535e99d2b2df4343ec2686b23b3a9740", size = 141265, upload-time = "2025-06-23T23:20:20.004Z" }, + { url = "https://files.pythonhosted.org/packages/40/9f/803f016eb9d514cd3f0aeb3dd4b06066af7dd2b7d6fb315bfce7926240a7/bitarray-3.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eb6d5b1c2d544e2691a9d519bdbbbc41630e71f0f5d3b4b02e072b1ecf7fe78a", size = 140680, upload-time = "2025-06-23T23:20:21.293Z" }, + { url = "https://files.pythonhosted.org/packages/41/ac/4cb6e0dd359e0c8498414ba9efc259a11cc5ae8463b3c9b4ec1ca1839945/bitarray-3.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e2b416291ba7817219296d2911fe06771b620541af26e6a4cc75e3559316d0af", size = 137524, upload-time = "2025-06-23T23:20:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/5e/08/4ad3f7cec01969c09f67da73023706e1661bf5a005afad9b7cfec73c6c6a/bitarray-3.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0522e602faf527e028a973e6260f2b6259a47d79fe8ddbf81b5176af36935e4", size = 317200, upload-time = "2025-06-23T23:20:24.707Z" }, + { url = "https://files.pythonhosted.org/packages/bf/55/19bc4d553654644623e9ae4b1381de9c67ae1e54d5b9a95c6ea48f46c950/bitarray-3.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd6b925a010d2749cba456ecd843f633594855f356d3ae66c49eb8cc6b3e0ba7", size = 332827, upload-time = "2025-06-23T23:20:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8a/0f7a3e971370fabb40c99a65145c3ae6f21dd858513f761a5d59f646d5cb/bitarray-3.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8dc18fb4e24affd29fbaf48a2c6714fa3dece01b7e06d7f0bb75a187f8f5cd", size = 326301, upload-time = "2025-06-23T23:20:28.308Z" }, + { url = "https://files.pythonhosted.org/packages/e1/cf/e35d81eabd1130e2725619106f9abf85f38bd140ce583e4ce4006a616d78/bitarray-3.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e9ea27c5f877d6abeb02ee6affcf97804829b35a640c52a0e4ae340e401c9e", size = 319172, upload-time = "2025-06-23T23:20:29.722Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8a/e26e3478c506191e31d3e3f56011e2874afa232412765d3bb77777556b5e/bitarray-3.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3720b7e9816f61ff0dfa2d750c3cd2f989d1105d953606fb90471f45f5b8065", size = 306535, upload-time = "2025-06-23T23:20:31.199Z" }, + { url = "https://files.pythonhosted.org/packages/45/c1/c5b07a97ba12d1fe72a83372bfa25a06439ebe131c5ea9992120ef65c92b/bitarray-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:09abb161caada9ae4cd556c7d2f4d430f8eb2a8248f2e3fa93d5eea799ed1563", size = 311403, upload-time = "2025-06-23T23:20:33.068Z" }, + { url = "https://files.pythonhosted.org/packages/0a/46/187875c5976a81d0e73db0ac017a36e8a9fe3d880c11c432e8fe3057326a/bitarray-3.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bfb3fee5054a9a266d2d3d274987fbc5d75893ba8d28b849d6ffbdaefcad30f1", size = 302970, upload-time = "2025-06-23T23:20:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/24/36/88838419c29feefae55b7ca41db30c72f487fbb0bea5bd3de39cecc1af25/bitarray-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a4eed600da6de44f260d440a60c55722beacd9908a4a2d6550323e74a9bbbbd8", size = 327525, upload-time = "2025-06-23T23:20:36.233Z" }, + { url = "https://files.pythonhosted.org/packages/b1/cc/88fbedbb3c6b1432c425915777026d9030906c3a92630d18f74f4206efa1/bitarray-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:98effdef57d3c60c3df67f203ee83b0716edd50b3ef9afaf1ae6468e5204c78f", size = 331324, upload-time = "2025-06-23T23:20:37.598Z" }, + { url = "https://files.pythonhosted.org/packages/ff/25/4e4806ac9b2497699698d6a0660d5323c082657e2259c5df96e6d2e2140a/bitarray-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71fe2e56394f81ed4d027938cf165f12b684c1d88fede535297f5ac94f54f5a0", size = 311275, upload-time = "2025-06-23T23:20:39.345Z" }, + { url = "https://files.pythonhosted.org/packages/ce/14/2400f4b9cddcf19ccd4d6ed3732bb700cb1909423cbe0b23f643e5ee5ba1/bitarray-3.4.3-cp312-cp312-win32.whl", hash = "sha256:28ea1d79c13a8443cdacf8711471d407ad402d55dac457a634be2dd739589a66", size = 134622, upload-time = "2025-06-23T23:20:41.036Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/54a8b7e498a5fbaeb3cf71537968884e0899410c4b33b208680da630a5c5/bitarray-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:ccb0bdca279d29286ef9bd973a975217927dfa7e0f0d6eac956df5b32ff7c57d", size = 141471, upload-time = "2025-06-23T23:20:42.274Z" }, + { url = "https://files.pythonhosted.org/packages/4b/dc/c0a56c0a01cbf36ac2d988f48ccbd4caf7fc78a8eeffea3046ceea17adfe/bitarray-3.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2162e97bbdb3a9d2dbf039df02baf9eefd2c13149fc615a5ce5a0189bff82fd4", size = 140655, upload-time = "2025-06-23T23:20:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/2b/62/5c10ba0ccf340e6744aef26135cef61ea7d0756e234ad9b175d2490e91c3/bitarray-3.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:254ab82798faf4636ffd3b5bfe2bf24ee6f70e0c8b794491da24f143329bf4c5", size = 137514, upload-time = "2025-06-23T23:20:45.232Z" }, + { url = "https://files.pythonhosted.org/packages/44/ba/6847f426473c02917cf5784c49dd4a5411cdf2aec1ca9df8fcdd98fcd2b8/bitarray-3.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9342795493deacc6bea42782fea777e180abb28cf2440e743f6c52b65b4bfddd", size = 317153, upload-time = "2025-06-23T23:20:47.556Z" }, + { url = "https://files.pythonhosted.org/packages/61/fe/2919d90da6fb81044d2ff5565ab7e85f1005ba8d1f65fca6cd914d4cab33/bitarray-3.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28347e92310a042e958c53336b03bea7e3eec451411ed0e27180d19c428ad7f2", size = 332706, upload-time = "2025-06-23T23:20:49.29Z" }, + { url = "https://files.pythonhosted.org/packages/60/9e/4d8a901744d28a17735050ac3564ee9d28b34885c772d9321f7af63e6944/bitarray-3.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f873f4506649575495ffc91febf3e573eabdb7b800e96326533a711807bbe7df", size = 326200, upload-time = "2025-06-23T23:20:51.176Z" }, + { url = "https://files.pythonhosted.org/packages/65/00/354655103f670c8051b10f597e8c70ba1959a92e9e73fa81ad246786b1e7/bitarray-3.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e9d9df7558497c72e832b1a29a1d3ec394c50c79829755b6237f9a76146f5e2", size = 319054, upload-time = "2025-06-23T23:20:52.652Z" }, + { url = "https://files.pythonhosted.org/packages/73/40/6ef40ca1b1d96dfe4102b96f3e0bf544f5872fae6a744f0a3aac649a9217/bitarray-3.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f849e428dd2c8c38b705e28b2baa6601fc9013e3a8dd4b922f128e474bcf687d", size = 306379, upload-time = "2025-06-23T23:20:54.024Z" }, + { url = "https://files.pythonhosted.org/packages/75/6e/46f2debcfa1ebffca1ae7e5644375c551618eda192dd0481df21e78e5e92/bitarray-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:438c50f2f9a5751fb12b1ae5c6283c94fc420c191ecd97f0d37483b3f1674a61", size = 311409, upload-time = "2025-06-23T23:20:55.796Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bf/bc0d5f371ea3a65d615663fc8f3ee03a2c1fade9bc18133504e60cbef2b4/bitarray-3.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cf5bdce1e64eb77cb10fd1046ec7ccd84a3e68cdeaf05da300adfc0a5ddcfa5", size = 302976, upload-time = "2025-06-23T23:20:57.251Z" }, + { url = "https://files.pythonhosted.org/packages/a4/cf/c851e57a8bd681fe77086630330b8f374616dba3c676aaeb278e0cac8d34/bitarray-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2e981af7e4e0d33de3cd7132619c04484cc83846922507855d6d167ae2c444b5", size = 327525, upload-time = "2025-06-23T23:20:58.815Z" }, + { url = "https://files.pythonhosted.org/packages/62/0b/8868d01a41bd486736d75009e80122e67b453e07520b4565c81f2f79e50f/bitarray-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:da717081402de4b5d66865c9989cb586076773a11af85324fdad4db6950d36a4", size = 331270, upload-time = "2025-06-23T23:21:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a2/4e92ee5daf21ed200e31ee07b2f305c413332f1d54c51c8478c765414b20/bitarray-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d6b1a7764e178b127e1388015c00bbc20d4e7188129532c530f1a12979c491f2", size = 311288, upload-time = "2025-06-23T23:21:01.901Z" }, + { url = "https://files.pythonhosted.org/packages/89/b2/1152782423029d5af578069870e451c9d9589ffa63464c76fe0385f82f52/bitarray-3.4.3-cp313-cp313-win32.whl", hash = "sha256:23ec148e5db67efee6376eefc0d167d4a25610b9e333b05e4ccfdcf7c2ac8a9a", size = 134634, upload-time = "2025-06-23T23:21:03.66Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3c/00b002c5df85f30b9eb598edabcb8e10728d77014f2d04e38ec31b369be1/bitarray-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:c3113d40de1adfd3c4f08e4bb0a69ff88807085cf2916138f2b55839c9d8d1b2", size = 141522, upload-time = "2025-06-23T23:21:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/0e/bf/d5b201dd97db50cd00a83c3762570d07a577cbdbfc4872fbd0968a1ad3ba/bitarray-3.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4ebc3675359381d70944cf6ca1f17eaeaa191f62cf78e178b1fbae10e3b89128", size = 140668, upload-time = "2025-06-23T23:21:50.387Z" }, + { url = "https://files.pythonhosted.org/packages/cf/1e/19594d860bf8531db6d6b7138827b3846d6f7ccd1d3bf9bed293916aafd3/bitarray-3.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c8535abd8f53333c382ba3d992206d7bd7ac91ea19ee3c712c18d5d750b8880", size = 137282, upload-time = "2025-06-23T23:21:51.799Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/99baeb1662b48012f2c472f9cef84f4f887e46c6e13b6aa5fb4e785a4c50/bitarray-3.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6232646581b2dc146e13985a6213c3fcbd9ccc7311e599fc01bdec086cffaad6", size = 306995, upload-time = "2025-06-23T23:21:53.41Z" }, + { url = "https://files.pythonhosted.org/packages/ed/53/2dd48a0210d73a3763374a934a647a3090be4907aff6dc3cc41465e333ae/bitarray-3.4.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78415356052fdcebd8943ef72a2f2eab7722296bdb7172c4127c213ad7cd5582", size = 322457, upload-time = "2025-06-23T23:21:54.979Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/b9651ca3917f6209badee62ebf00d8e784c07324f9a41b67db1c235bf4aa/bitarray-3.4.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52bfc007309a6ef569a0c2add27abde97ac384657377c993ae34003b05568000", size = 314488, upload-time = "2025-06-23T23:21:56.551Z" }, + { url = "https://files.pythonhosted.org/packages/83/b2/2d1f65f7013759e1c288d099d492623505fe2fa5ed8d7e3d0a9d262d277f/bitarray-3.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c80319c7e455d8a35f33133e2db35daefcd10ec32d0283e0c45b5b3c8f2b7486", size = 307984, upload-time = "2025-06-23T23:21:58.233Z" }, + { url = "https://files.pythonhosted.org/packages/12/9a/c8855f65cd07c494662c289399a2131d65c45723774ee8bea8b86f8bd469/bitarray-3.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63a51d3831caa098f8fda1b128ad78a68b945e92a1951cba3e623afa4fc7a825", size = 295953, upload-time = "2025-06-23T23:22:00.124Z" }, + { url = "https://files.pythonhosted.org/packages/ed/78/19844b3f5b40793ab4ee83849a5340bacf414d51b9313a9b429d5d0b0cdc/bitarray-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f0d5a19de5bf8d79c498ea53b6625a103b696d5c096eb1424e265e3ffd7499e7", size = 299325, upload-time = "2025-06-23T23:22:02.005Z" }, + { url = "https://files.pythonhosted.org/packages/15/a6/4d90c44b730c248f2fdb0e9b06446fd98b368a11b8bdd3aae72f1222a215/bitarray-3.4.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3c08df34aeb454501cbf4a678e29f5ce6e6b532b9203c1055c5e7801237b43c7", size = 293980, upload-time = "2025-06-23T23:22:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/db/a1/07845a309e01bcfb0051950c7d95781d3536fa20dfe111f0594b6897628d/bitarray-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:a19ec17ca5c8d5eee9b66f4e5022dfc1501db5771893eb8c192163a6cb0512b3", size = 316697, upload-time = "2025-06-23T23:22:05.082Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8a/2ca42019c405d71e9c9e628b82ea58ba2269d742a78d2d522a8516fff199/bitarray-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:1d3fd4f52270e153324d7cb481dd5e956101b753232beeb6f9196b64eb3f29f1", size = 319001, upload-time = "2025-06-23T23:22:06.742Z" }, + { url = "https://files.pythonhosted.org/packages/d9/bf/801e4f7dc0710c9619004f71e2627128ab2a017c61a55ca86834664256fa/bitarray-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ec9483cc157bb6f00a80bd02193277b583725f530105d7e82452321514763eca", size = 299665, upload-time = "2025-06-23T23:22:08.665Z" }, + { url = "https://files.pythonhosted.org/packages/7a/02/3e40fdf38fe5997e315d2c92b8bce9bc450752b29b300bd442e631324e71/bitarray-3.4.3-cp38-cp38-win32.whl", hash = "sha256:9998473d6e58fa94425ec6f7878098020bb92cdf51f629f61282aa1f4b505e78", size = 132691, upload-time = "2025-06-23T23:22:10.219Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7b/100116b6a6d1105526a0860ef97a133c0767e80298a1a6882df5c741cbb4/bitarray-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:02b2f2e7d50da4a8d41c5d2c5e4847c0666ae906de7955b1b5c799e85546abfa", size = 139227, upload-time = "2025-06-23T23:22:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/f4/13/51afc82ede8caa93ff1038275d038d9857d13fabd0adfd590680c633e2ef/bitarray-3.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:187f181aa45ee92be7af539ebccde50ccc5e6ac1b07c3ffa681460f0bc4bd2a6", size = 140965, upload-time = "2025-06-23T23:22:13.313Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1e/0286997f1d6e8ceb1d8d002bcf903de7a8b72392a43b5300061645588257/bitarray-3.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab1c03490011041ba2d787404cd7a8364acbc86732bff866ab8d67d81bde9af5", size = 137722, upload-time = "2025-06-23T23:22:14.786Z" }, + { url = "https://files.pythonhosted.org/packages/bc/db/75a6263d4c1e52b6dda651be45af94d85e07bb6131446413c114d280fb6c/bitarray-3.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddd2343819c150da13a48eb5de3a01a687453541af7e28104fcc85dd4d98b052", size = 304118, upload-time = "2025-06-23T23:22:16.469Z" }, + { url = "https://files.pythonhosted.org/packages/64/7e/f178ec4795364f457d266e9ba95af9931650591eaef9fa0968ffa30cafb4/bitarray-3.4.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45433df04bd446c0fe35c2f9013067439291050ba775e77b4df019382817bc43", size = 320605, upload-time = "2025-06-23T23:22:17.999Z" }, + { url = "https://files.pythonhosted.org/packages/45/c8/1aad32828021ffbf59aa5a266e3f411e9fd0dd11e65b9341b3a44c64aec3/bitarray-3.4.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc3f2e986a1728337b4cec6ac126abfee14bd91a1060a136861aa551ed716320", size = 312347, upload-time = "2025-06-23T23:22:19.958Z" }, + { url = "https://files.pythonhosted.org/packages/11/ed/b86565a32f0e11701b52a4a0b03831d73dad7e94764f40b1eb4fddc93c79/bitarray-3.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:024295d1d89835588c5760dc72cd74c445c2313ef443225d89b430ed523ad618", size = 305954, upload-time = "2025-06-23T23:22:22.034Z" }, + { url = "https://files.pythonhosted.org/packages/10/1e/8bbd4797ddc691f0be6fdd1737d67ff5765b2301b2721c91cd2ff951e6f0/bitarray-3.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb0ce1bec34e4dbe9de3481d30675666ad77475378295763ab4b7387ad31a9e", size = 293967, upload-time = "2025-06-23T23:22:23.983Z" }, + { url = "https://files.pythonhosted.org/packages/71/5e/dcf5d61f012f6bc19a6f9aa1161ef4f7cde6e9dc4e1c0a8f59e546e010fe/bitarray-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f3134a29d349b6ee4e472394f934de47cdb44373b53ceb17a44ffc2b10e683fc", size = 298991, upload-time = "2025-06-23T23:22:25.602Z" }, + { url = "https://files.pythonhosted.org/packages/1e/07/bde95d7f89c01493065373642a7704f1cb0efa0080ba13f95d3b1f391bd9/bitarray-3.4.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4badb5134887a4f755d8e5278cd8a761d7fa9fe746ceb46945ecea4071547593", size = 291168, upload-time = "2025-06-23T23:22:27.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/99/c0a5b7f3ab56d3f5d4e78d41117a67e03c407fe02c08354049159e1c38d8/bitarray-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:db762c9c05ebb0cabf90739e37acc0e071c65726efa3321a59d7385aaf5eaea3", size = 315933, upload-time = "2025-06-23T23:22:29.528Z" }, + { url = "https://files.pythonhosted.org/packages/22/2d/930c2eae9c9670369b2cb112a3d3385af1d4d55489a704d1d637403bc3a7/bitarray-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:00ccedbe1d66d8c478bf0bfb9ba66b0fc9b8f3609547cd4d81afe019dd0b2501", size = 318562, upload-time = "2025-06-23T23:22:31.183Z" }, + { url = "https://files.pythonhosted.org/packages/ad/2d/647518738251ba18016c99cefd2a73a9dea6e7a93a7277e54631639408db/bitarray-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4c2c807bad952222b57759d5b9c68db7978a79c086e550d160ebea7ec3a47a62", size = 298299, upload-time = "2025-06-23T23:22:32.788Z" }, + { url = "https://files.pythonhosted.org/packages/de/6f/98491e31d58df1695deab60aeafcf97646f7a9f168715d49fe2e44db50f0/bitarray-3.4.3-cp39-cp39-win32.whl", hash = "sha256:a2ea7b16091655029926c5b45bd7db7228f4fc50939fdb52b0849ea8442bd78b", size = 134368, upload-time = "2025-06-23T23:22:34.794Z" }, + { url = "https://files.pythonhosted.org/packages/4b/86/593b90476c2cee5fd3cc41bcffae5cb401355d8f930d450cc026be1f95fd/bitarray-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:fa9e751049688bce4cf6901a4847d829146d9d618b539f14d960e072b778f212", size = 140913, upload-time = "2025-06-23T23:22:36.282Z" }, + { url = "https://files.pythonhosted.org/packages/81/d1/e37f850525cbb2c4d539a57217e26c0bdeb7fb5082e8e9da9c799ecf415d/bitarray-3.4.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:988fa0b40aa939be4d171fab1e29d437523d72919116906ece5a39e5d2ed80c8", size = 135990, upload-time = "2025-06-23T23:22:37.904Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4b/f885e1256d4ab5cf04d4d7441615e0997f21bd4163b2fe3f1d2410dc44a8/bitarray-3.4.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b6a80cbe9ee4dc4994d1a7f59d684df1fef27adb018a12342dcf2404522f8a8", size = 132877, upload-time = "2025-06-23T23:22:39.806Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/73b7991557c82b573272e856a6ae61fea8a1c8760dbe546aef01ece64051/bitarray-3.4.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:353c17eefe044b56ecbfa90c69ce579d88b3bf1527285d4487170ba619dcff75", size = 141550, upload-time = "2025-06-23T23:22:41.499Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3f/763b240ba9c8ddd65bd327156ee37849429373dac6685c4d70254c6bbd3d/bitarray-3.4.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6e9cd32323ab76fb9f88379176d89a2815dae8f941c91a16b2f8f5af7ee5463", size = 142292, upload-time = "2025-06-23T23:22:43.008Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/5d7ed813ced13846a9048601ded3fbd468ee9f45bde3b712bb222a718987/bitarray-3.4.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a2c06cf52f8ebd6b66651f1ee24fe1c006046970bbfc436e0132917a0c51a266", size = 143889, upload-time = "2025-06-23T23:22:44.541Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a0/9557347eb30f2bb36a1ff9e5a662438fdf93b4247035f840d230955b567c/bitarray-3.4.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:54e4c8527d3b642918c2510433bd8fe53808b149dde4e293178da0d6c0b7e39b", size = 139817, upload-time = "2025-06-23T23:22:46.391Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/bf6ad62fff314ecc281d663457b8e25fc702834eca360954a0abe1d7bb18/bitarray-3.4.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e74d09f7d9febcec01de4a27fc600d93b9de769c9e58c3f28ac0f24a7276ba1c", size = 135666, upload-time = "2025-06-23T23:22:58.104Z" }, + { url = "https://files.pythonhosted.org/packages/37/9d/940dfa5f89c3292f56ff24ad8d631c0298b9e4d3dd908966cdae36c8c5bb/bitarray-3.4.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0e57449ad43beb6fa344501f445f2b1d7689405127b8975d53ba0c320d4f6743", size = 132779, upload-time = "2025-06-23T23:22:59.919Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c2/becb00b8d7045f0bfb3d790e0ae7c4f7308c5d62a33e7882a9e59798b9a3/bitarray-3.4.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e7c58d1a25acc829626ece2608ab21c2466bdbd0433c1a7caa2529c4641169d", size = 141242, upload-time = "2025-06-23T23:23:01.866Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/4c8ae7d189cff93bcb6c0861a277e77fdc1418a5bb636c8384d98f2620bf/bitarray-3.4.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d043f4af52530d23ebd60e664a95041e8707ffeb283a5f604beb964ff6d5c7c", size = 142069, upload-time = "2025-06-23T23:23:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/1e18cf2139eff317c0c98872e182e634bec989954f10531af48d58c899a4/bitarray-3.4.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:137c14f9a9c81298973eb19c6a3af2ed794d5978645223e3c7b37aa2f11fe897", size = 143549, upload-time = "2025-06-23T23:23:05.769Z" }, + { url = "https://files.pythonhosted.org/packages/83/13/d489e06079d5a05dbf7893e914fa04ab558cc58e2e6cf0d947950d5c9e7a/bitarray-3.4.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1f170df5c24d0101410435fc210b68e55d800a77a2a26a3fd3e7ee9ab882337", size = 139775, upload-time = "2025-06-23T23:23:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/a217c8f201d849e57aca4191eeb5278bd04f84f321443f7a92d51973bcd8/bitarray-3.4.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ee46b605100c722550bd7a216dcba4854f1d34c6632764049908da7ebaf8c83b", size = 136023, upload-time = "2025-06-23T23:23:09.376Z" }, + { url = "https://files.pythonhosted.org/packages/18/85/648ac84978b7961ea6375a9f18f57d422b788dd83a81598b4f369f7cf85e/bitarray-3.4.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:549caacd684e1120f446e0cf35b7faa06e9f4b647195b6ba341556d8d02a1ecf", size = 132997, upload-time = "2025-06-23T23:23:11.356Z" }, + { url = "https://files.pythonhosted.org/packages/51/37/12795c9624b85a6ce80f8c61af96fe6f7e6108a73a22cd95df98133e4dca/bitarray-3.4.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0924c210915c4d76cea002ee7f5138deb5d44e70eafe7ab4652fc7485d57803", size = 141589, upload-time = "2025-06-23T23:23:13.314Z" }, + { url = "https://files.pythonhosted.org/packages/ae/84/70f8388f9004618cc8f972f5d582bde38665f0824c4c1864477c8d16d8bf/bitarray-3.4.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7354817ae65370aba12d67c953c792ae0a757c12808629a49a003f2e4cab5a7f", size = 142326, upload-time = "2025-06-23T23:23:15.043Z" }, + { url = "https://files.pythonhosted.org/packages/89/96/8c449a2c1ad2b28d241384f743a52a0b5aef5d90fa8e329f59947d46b0b4/bitarray-3.4.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2584f4d9d30de1b02e0b8f1256fa1d4c8a3e4346749fbc61ec430f210739ec15", size = 143826, upload-time = "2025-06-23T23:23:16.688Z" }, + { url = "https://files.pythonhosted.org/packages/8f/93/4a031dbee0fb1e2b260195eb77113d0a5c76fc564622621db424dde169cd/bitarray-3.4.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:df9447d1a9fad5d935bfe8a0263cb935f089b2a866a5d7a32ba09dc69671c812", size = 139861, upload-time = "2025-06-23T23:23:18.713Z" }, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fd/f700cfd4ad876def96d2c769d8a32d808b12d1010b6003dc6639157f99ee/charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", size = 198257, upload-time = "2025-05-02T08:33:45.511Z" }, + { url = "https://files.pythonhosted.org/packages/3a/95/6eec4cbbbd119e6a402e3bfd16246785cc52ce64cf21af2ecdf7b3a08e91/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", size = 143453, upload-time = "2025-05-02T08:33:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b3/d4f913660383b3d93dbe6f687a312ea9f7e89879ae883c4e8942048174d4/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", size = 153130, upload-time = "2025-05-02T08:33:50.568Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/7540141529eabc55bf19cc05cd9b61c2078bebfcdbd3e799af99b777fc28/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", size = 145688, upload-time = "2025-05-02T08:33:52.828Z" }, + { url = "https://files.pythonhosted.org/packages/2e/bb/d76d3d6e340fb0967c43c564101e28a78c9a363ea62f736a68af59ee3683/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", size = 147418, upload-time = "2025-05-02T08:33:54.718Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ef/b7c1f39c0dc3808160c8b72e0209c2479393966313bfebc833533cfff9cc/charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", size = 150066, upload-time = "2025-05-02T08:33:56.597Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/4e47cc23d2a4a5eb6ed7d6f0f8cda87d753e2f8abc936d5cf5ad2aae8518/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", size = 144499, upload-time = "2025-05-02T08:33:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/d7/9c/efdf59dd46593cecad0548d36a702683a0bdc056793398a9cd1e1546ad21/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", size = 152954, upload-time = "2025-05-02T08:34:00.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/b3/4e8b73f7299d9aaabd7cd26db4a765f741b8e57df97b034bb8de15609002/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", size = 155876, upload-time = "2025-05-02T08:34:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/53/cb/6fa0ccf941a069adce3edb8a1e430bc80e4929f4d43b5140fdf8628bdf7d/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", size = 153186, upload-time = "2025-05-02T08:34:04.481Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c6/80b93fabc626b75b1665ffe405e28c3cef0aae9237c5c05f15955af4edd8/charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", size = 148007, upload-time = "2025-05-02T08:34:06.888Z" }, + { url = "https://files.pythonhosted.org/packages/41/eb/c7367ac326a2628e4f05b5c737c86fe4a8eb3ecc597a4243fc65720b3eeb/charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", size = 97923, upload-time = "2025-05-02T08:34:08.792Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/1c82646582ccf2c757fa6af69b1a3ea88744b8d2b4ab93b7686b2533e023/charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", size = 105020, upload-time = "2025-05-02T08:34:10.6Z" }, + { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" }, + { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" }, + { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" }, + { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" }, + { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" }, + { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" }, + { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690, upload-time = "2024-08-04T19:43:07.695Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127, upload-time = "2024-08-04T19:43:10.15Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654, upload-time = "2024-08-04T19:43:12.405Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598, upload-time = "2024-08-04T19:43:14.078Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732, upload-time = "2024-08-04T19:43:16.632Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816, upload-time = "2024-08-04T19:43:19.049Z" }, + { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325, upload-time = "2024-08-04T19:43:21.246Z" }, + { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418, upload-time = "2024-08-04T19:43:22.945Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343, upload-time = "2024-08-04T19:43:25.121Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136, upload-time = "2024-08-04T19:43:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796, upload-time = "2024-08-04T19:43:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244, upload-time = "2024-08-04T19:43:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279, upload-time = "2024-08-04T19:43:33.581Z" }, + { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859, upload-time = "2024-08-04T19:43:35.301Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549, upload-time = "2024-08-04T19:43:37.578Z" }, + { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477, upload-time = "2024-08-04T19:43:39.92Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134, upload-time = "2024-08-04T19:43:41.453Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910, upload-time = "2024-08-04T19:43:43.037Z" }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348, upload-time = "2024-08-04T19:43:44.787Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230, upload-time = "2024-08-04T19:43:46.707Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007, upload-time = "2024-08-04T19:44:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269, upload-time = "2024-08-04T19:44:11.045Z" }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886, upload-time = "2024-08-04T19:44:12.83Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037, upload-time = "2024-08-04T19:44:15.393Z" }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038, upload-time = "2024-08-04T19:44:17.466Z" }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690, upload-time = "2024-08-04T19:44:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765, upload-time = "2024-08-04T19:44:20.994Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611, upload-time = "2024-08-04T19:44:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671, upload-time = "2024-08-04T19:44:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368, upload-time = "2024-08-04T19:44:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758, upload-time = "2024-08-04T19:44:29.028Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035, upload-time = "2024-08-04T19:44:30.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839, upload-time = "2024-08-04T19:44:32.412Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569, upload-time = "2024-08-04T19:44:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927, upload-time = "2024-08-04T19:44:36.313Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401, upload-time = "2024-08-04T19:44:38.155Z" }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301, upload-time = "2024-08-04T19:44:39.883Z" }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598, upload-time = "2024-08-04T19:44:41.59Z" }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307, upload-time = "2024-08-04T19:44:43.301Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453, upload-time = "2024-08-04T19:44:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674, upload-time = "2024-08-04T19:44:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101, upload-time = "2024-08-04T19:44:49.32Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554, upload-time = "2024-08-04T19:44:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440, upload-time = "2024-08-04T19:44:53.464Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889, upload-time = "2024-08-04T19:44:55.165Z" }, + { url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142, upload-time = "2024-08-04T19:44:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805, upload-time = "2024-08-04T19:44:59.033Z" }, + { url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655, upload-time = "2024-08-04T19:45:01.398Z" }, + { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296, upload-time = "2024-08-04T19:45:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137, upload-time = "2024-08-04T19:45:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688, upload-time = "2024-08-04T19:45:08.358Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120, upload-time = "2024-08-04T19:45:11.526Z" }, + { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249, upload-time = "2024-08-04T19:45:13.202Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237, upload-time = "2024-08-04T19:45:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311, upload-time = "2024-08-04T19:45:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453, upload-time = "2024-08-04T19:45:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958, upload-time = "2024-08-04T19:45:20.63Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938, upload-time = "2024-08-04T19:45:23.062Z" }, + { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352, upload-time = "2024-08-04T19:45:25.042Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153, upload-time = "2024-08-04T19:45:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926, upload-time = "2024-08-04T19:45:28.875Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version < '3.9'" }, +] + +[[package]] +name = "coverage" +version = "7.9.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload-time = "2025-07-03T10:54:15.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/0d/5c2114fd776c207bd55068ae8dc1bef63ecd1b767b3389984a8e58f2b926/coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912", size = 212039, upload-time = "2025-07-03T10:52:38.955Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ad/dc51f40492dc2d5fcd31bb44577bc0cc8920757d6bc5d3e4293146524ef9/coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f", size = 212428, upload-time = "2025-07-03T10:52:41.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a3/55cb3ff1b36f00df04439c3993d8529193cdf165a2467bf1402539070f16/coverage-7.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f", size = 241534, upload-time = "2025-07-03T10:52:42.956Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c9/a8410b91b6be4f6e9c2e9f0dce93749b6b40b751d7065b4410bf89cb654b/coverage-7.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf", size = 239408, upload-time = "2025-07-03T10:52:44.199Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c4/6f3e56d467c612b9070ae71d5d3b114c0b899b5788e1ca3c93068ccb7018/coverage-7.9.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547", size = 240552, upload-time = "2025-07-03T10:52:45.477Z" }, + { url = "https://files.pythonhosted.org/packages/fd/20/04eda789d15af1ce79bce5cc5fd64057c3a0ac08fd0576377a3096c24663/coverage-7.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45", size = 240464, upload-time = "2025-07-03T10:52:46.809Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5a/217b32c94cc1a0b90f253514815332d08ec0812194a1ce9cca97dda1cd20/coverage-7.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2", size = 239134, upload-time = "2025-07-03T10:52:48.149Z" }, + { url = "https://files.pythonhosted.org/packages/34/73/1d019c48f413465eb5d3b6898b6279e87141c80049f7dbf73fd020138549/coverage-7.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e", size = 239405, upload-time = "2025-07-03T10:52:49.687Z" }, + { url = "https://files.pythonhosted.org/packages/49/6c/a2beca7aa2595dad0c0d3f350382c381c92400efe5261e2631f734a0e3fe/coverage-7.9.2-cp310-cp310-win32.whl", hash = "sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e", size = 214519, upload-time = "2025-07-03T10:52:51.036Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c8/91e5e4a21f9a51e2c7cdd86e587ae01a4fcff06fc3fa8cde4d6f7cf68df4/coverage-7.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c", size = 215400, upload-time = "2025-07-03T10:52:52.313Z" }, + { url = "https://files.pythonhosted.org/packages/39/40/916786453bcfafa4c788abee4ccd6f592b5b5eca0cd61a32a4e5a7ef6e02/coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba", size = 212152, upload-time = "2025-07-03T10:52:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/9f/66/cc13bae303284b546a030762957322bbbff1ee6b6cb8dc70a40f8a78512f/coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa", size = 212540, upload-time = "2025-07-03T10:52:55.196Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3c/d56a764b2e5a3d43257c36af4a62c379df44636817bb5f89265de4bf8bd7/coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a", size = 245097, upload-time = "2025-07-03T10:52:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/b1/46/bd064ea8b3c94eb4ca5d90e34d15b806cba091ffb2b8e89a0d7066c45791/coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc", size = 242812, upload-time = "2025-07-03T10:52:57.842Z" }, + { url = "https://files.pythonhosted.org/packages/43/02/d91992c2b29bc7afb729463bc918ebe5f361be7f1daae93375a5759d1e28/coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2", size = 244617, upload-time = "2025-07-03T10:52:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4f/8fadff6bf56595a16d2d6e33415841b0163ac660873ed9a4e9046194f779/coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c", size = 244263, upload-time = "2025-07-03T10:53:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d2/e0be7446a2bba11739edb9f9ba4eff30b30d8257370e237418eb44a14d11/coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd", size = 242314, upload-time = "2025-07-03T10:53:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7d/dcbac9345000121b8b57a3094c2dfcf1ccc52d8a14a40c1d4bc89f936f80/coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74", size = 242904, upload-time = "2025-07-03T10:53:03.478Z" }, + { url = "https://files.pythonhosted.org/packages/41/58/11e8db0a0c0510cf31bbbdc8caf5d74a358b696302a45948d7c768dfd1cf/coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6", size = 214553, upload-time = "2025-07-03T10:53:05.174Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7d/751794ec8907a15e257136e48dc1021b1f671220ecccfd6c4eaf30802714/coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7", size = 215441, upload-time = "2025-07-03T10:53:06.472Z" }, + { url = "https://files.pythonhosted.org/packages/62/5b/34abcedf7b946c1c9e15b44f326cb5b0da852885312b30e916f674913428/coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62", size = 213873, upload-time = "2025-07-03T10:53:07.699Z" }, + { url = "https://files.pythonhosted.org/packages/53/d7/7deefc6fd4f0f1d4c58051f4004e366afc9e7ab60217ac393f247a1de70a/coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0", size = 212344, upload-time = "2025-07-03T10:53:09.3Z" }, + { url = "https://files.pythonhosted.org/packages/95/0c/ee03c95d32be4d519e6a02e601267769ce2e9a91fc8faa1b540e3626c680/coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3", size = 212580, upload-time = "2025-07-03T10:53:11.52Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9f/826fa4b544b27620086211b87a52ca67592622e1f3af9e0a62c87aea153a/coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1", size = 246383, upload-time = "2025-07-03T10:53:13.134Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b3/4477aafe2a546427b58b9c540665feff874f4db651f4d3cb21b308b3a6d2/coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615", size = 243400, upload-time = "2025-07-03T10:53:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c2/efffa43778490c226d9d434827702f2dfbc8041d79101a795f11cbb2cf1e/coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b", size = 245591, upload-time = "2025-07-03T10:53:15.872Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e7/a59888e882c9a5f0192d8627a30ae57910d5d449c80229b55e7643c078c4/coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9", size = 245402, upload-time = "2025-07-03T10:53:17.124Z" }, + { url = "https://files.pythonhosted.org/packages/92/a5/72fcd653ae3d214927edc100ce67440ed8a0a1e3576b8d5e6d066ed239db/coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f", size = 243583, upload-time = "2025-07-03T10:53:18.781Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f5/84e70e4df28f4a131d580d7d510aa1ffd95037293da66fd20d446090a13b/coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d", size = 244815, upload-time = "2025-07-03T10:53:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/39/e7/d73d7cbdbd09fdcf4642655ae843ad403d9cbda55d725721965f3580a314/coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355", size = 214719, upload-time = "2025-07-03T10:53:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d6/7486dcc3474e2e6ad26a2af2db7e7c162ccd889c4c68fa14ea8ec189c9e9/coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0", size = 215509, upload-time = "2025-07-03T10:53:22.853Z" }, + { url = "https://files.pythonhosted.org/packages/b7/34/0439f1ae2593b0346164d907cdf96a529b40b7721a45fdcf8b03c95fcd90/coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b", size = 213910, upload-time = "2025-07-03T10:53:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload-time = "2025-07-03T10:53:25.811Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload-time = "2025-07-03T10:53:27.075Z" }, + { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload-time = "2025-07-03T10:53:28.408Z" }, + { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload-time = "2025-07-03T10:53:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload-time = "2025-07-03T10:53:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload-time = "2025-07-03T10:53:32.717Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload-time = "2025-07-03T10:53:34.009Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload-time = "2025-07-03T10:53:35.434Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload-time = "2025-07-03T10:53:36.787Z" }, + { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload-time = "2025-07-03T10:53:38.188Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload-time = "2025-07-03T10:53:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload-time = "2025-07-03T10:53:40.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload-time = "2025-07-03T10:53:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload-time = "2025-07-03T10:53:43.823Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload-time = "2025-07-03T10:53:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload-time = "2025-07-03T10:53:46.931Z" }, + { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload-time = "2025-07-03T10:53:48.289Z" }, + { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload-time = "2025-07-03T10:53:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload-time = "2025-07-03T10:53:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload-time = "2025-07-03T10:53:52.808Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload-time = "2025-07-03T10:53:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload-time = "2025-07-03T10:53:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/62/ab/b4b06662ccaa00ca7bbee967b7035a33a58b41efb92d8c89a6c523a2ccd5/coverage-7.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddc39510ac922a5c4c27849b739f875d3e1d9e590d1e7b64c98dadf037a16cce", size = 212037, upload-time = "2025-07-03T10:53:58.055Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5e/04619995657acc898d15bfad42b510344b3a74d4d5bc34f2e279d46c781c/coverage-7.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a535c0c7364acd55229749c2b3e5eebf141865de3a8f697076a3291985f02d30", size = 212412, upload-time = "2025-07-03T10:53:59.451Z" }, + { url = "https://files.pythonhosted.org/packages/14/e7/1465710224dc6d31c534e7714cbd907210622a044adc81c810e72eea873f/coverage-7.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df0f9ef28e0f20c767ccdccfc5ae5f83a6f4a2fbdfbcbcc8487a8a78771168c8", size = 241164, upload-time = "2025-07-03T10:54:00.852Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f2/44c6fbd2794afeb9ab6c0a14d3c088ab1dae3dff3df2624609981237bbb4/coverage-7.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f3da12e0ccbcb348969221d29441ac714bbddc4d74e13923d3d5a7a0bebef7a", size = 239032, upload-time = "2025-07-03T10:54:02.25Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d2/7a79845429c0aa2e6788bc45c26a2e3052fa91082c9ea1dea56fb531952c/coverage-7.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a17eaf46f56ae0f870f14a3cbc2e4632fe3771eab7f687eda1ee59b73d09fe4", size = 240148, upload-time = "2025-07-03T10:54:03.618Z" }, + { url = "https://files.pythonhosted.org/packages/9c/7d/2731d1b4c9c672d82d30d218224dfc62939cf3800bc8aba0258fefb191f5/coverage-7.9.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:669135a9d25df55d1ed56a11bf555f37c922cf08d80799d4f65d77d7d6123fcf", size = 239875, upload-time = "2025-07-03T10:54:05.022Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/685958715429a9da09cf172c15750ca5c795dd7259466f2645403696557b/coverage-7.9.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9d3a700304d01a627df9db4322dc082a0ce1e8fc74ac238e2af39ced4c083193", size = 238127, upload-time = "2025-07-03T10:54:06.366Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/161a4313308b3783126790adfae1970adbe4886fda8788792e435249910a/coverage-7.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:71ae8b53855644a0b1579d4041304ddc9995c7b21c8a1f16753c4d8903b4dfed", size = 239064, upload-time = "2025-07-03T10:54:07.878Z" }, + { url = "https://files.pythonhosted.org/packages/17/14/fe33f41b2e80811021de059621f44c01ebe4d6b08bdb82d54a514488e933/coverage-7.9.2-cp39-cp39-win32.whl", hash = "sha256:dd7a57b33b5cf27acb491e890720af45db05589a80c1ffc798462a765be6d4d7", size = 214522, upload-time = "2025-07-03T10:54:09.331Z" }, + { url = "https://files.pythonhosted.org/packages/6e/30/63d850ec31b5c6f6a7b4e853016375b846258300320eda29376e2786ceeb/coverage-7.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f65bb452e579d5540c8b37ec105dd54d8b9307b07bcaa186818c104ffda22441", size = 215419, upload-time = "2025-07-03T10:54:10.681Z" }, + { url = "https://files.pythonhosted.org/packages/d7/85/f8bbefac27d286386961c25515431482a425967e23d3698b75a250872924/coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050", size = 204013, upload-time = "2025-07-03T10:54:12.084Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload-time = "2025-07-03T10:54:13.491Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", version = "4.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c4/6925ad41576d3e84f03aaf9a0411667af861f9fa2c87553c7dd5bde01518/pycryptodome-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:865d83c906b0fc6a59b510deceee656b6bc1c4fa0d82176e2b77e97a420a996a", size = 1623768, upload-time = "2025-05-17T17:21:33.418Z" }, + { url = "https://files.pythonhosted.org/packages/a8/14/d6c6a3098ddf2624068f041c5639be5092ad4ae1a411842369fd56765994/pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d4d56153efc4d81defe8b65fd0821ef8b2d5ddf8ed19df31ba2f00872b8002", size = 1672070, upload-time = "2025-05-17T17:21:35.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/89/5d29c8f178fea7c92fd20d22f9ddd532a5e3ac71c574d555d2362aaa832a/pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3f2d0aaf8080bda0587d58fc9fe4766e012441e2eed4269a77de6aea981c8be", size = 1664359, upload-time = "2025-05-17T17:21:37.551Z" }, + { url = "https://files.pythonhosted.org/packages/38/bc/a287d41b4421ad50eafb02313137d0276d6aeffab90a91e2b08f64140852/pycryptodome-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64093fc334c1eccfd3933c134c4457c34eaca235eeae49d69449dc4728079339", size = 1702359, upload-time = "2025-05-17T17:21:39.827Z" }, + { url = "https://files.pythonhosted.org/packages/2b/62/2392b7879f4d2c1bfa20815720b89d464687877851716936b9609959c201/pycryptodome-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ce64e84a962b63a47a592690bdc16a7eaf709d2c2697ababf24a0def566899a6", size = 1802461, upload-time = "2025-05-17T17:21:41.722Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, + { name = "iniconfig", marker = "python_full_version < '3.9'" }, + { name = "packaging", marker = "python_full_version < '3.9'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "tomli", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "iniconfig", marker = "python_full_version >= '3.9'" }, + { name = "packaging", marker = "python_full_version >= '3.9'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pygments", marker = "python_full_version >= '3.9'" }, + { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] +dependencies = [ + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.9'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042, upload-time = "2024-03-24T20:16:34.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990, upload-time = "2024-03-24T20:16:32.444Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "coverage", version = "7.9.2", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.9'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + +[[package]] +name = "uid2-client" +version = "3.0.0a1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bitarray" }, + { name = "pycryptodome" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/d0bccd77e2c7a05ef6afba5eb4b213e55e760c4396fbf8edc3f3865c08aa/uid2_client-3.0.0a1.tar.gz", hash = "sha256:8a7281fcdb2139390b5f03e94d8ca5d22960e08fe122a560f6498e0388703e6f", size = 51093, upload-time = "2024-06-21T18:52:19.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/a1/6ef65f97aa2e3f4387d3a4f3f6959b8fd78d01b24540fef8fc663aca81bc/uid2_client-3.0.0a1-py3-none-any.whl", hash = "sha256:0793ebdebeab0403d4f1c6e39339ca1319d12bb03b1733b50aa74d514fe08310", size = 39564, upload-time = "2024-06-21T18:52:17.977Z" }, +] + +[[package]] +name = "uid2-docs-python-tests" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "python-dotenv", version = "1.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "python-dotenv", version = "1.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "uid2-client" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest-cov", version = "6.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] + +[package.metadata] +requires-dist = [ + { name = "pytest", specifier = ">=7.0.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0.0" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "uid2-client", specifier = ">=3.0.0a1" }, +] +provides-extras = ["dev"] + +[[package]] +name = "urllib3" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.8.1' and python_full_version < '3.9'", + "python_full_version < '3.8.1'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload-time = "2024-09-12T10:52:18.401Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +]