From ba6535c00e279ee715c023071343bac437188415 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 21 Dec 2021 08:54:04 +0000 Subject: [PATCH 001/121] Version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2319c6c..f2ec9a7 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="flagsmith", - version="2.0.1", + version="3.0.0", packages=["flagsmith"], description="Flagsmith Python SDK", long_description=long_description, From ff243ca01d8c0713efea74bd96ec9ed4585c57f2 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 8 Feb 2022 16:41:47 +0000 Subject: [PATCH 002/121] Feature/rewrite for client side eval (#17) * Update setup.py * Add publish workflow * Add test workflow * Rewrite basic python client functionality * Add analytics processor logic * Add some docstrings and util function * Move to poetry for dependency management and builds * Formatting * Remove duplicate triggers for pytest workflow * Fix pipeline * Only include flagsmith package * Update example app * Add some todos * Change default refresh interval * Add ability to enable / disable analytics * Add defaults * Typehinting updates * Update example app to use defaults * Tidy up example app * Tidy up type hint * Fix environment document url --- .flake8 | 4 + .github/workflows/publish.yml | 24 + .github/workflows/pytest.yml | 16 +- example/.env.template | 2 + example/app.py | 55 ++ example/example.py | 58 -- example/readme.md | 27 +- example/requirements.txt | 2 + example/templates/home.html | 25 + flagsmith/__init__.py | 2 +- flagsmith/analytics.py | 1 - flagsmith/exceptions.py | 6 + flagsmith/flagsmith.py | 329 ++++---- flagsmith/models.py | 136 ++++ flagsmith/polling_manager.py | 27 + flagsmith/utils/__init__.py | 0 flagsmith/utils/identities.py | 5 + poetry.lock | 741 ++++++++++++++++++ pyproject.toml | 31 + requirements-dev.txt | 4 - requirements.txt | 2 - setup.cfg | 2 - setup.py | 36 - tests/conftest.py | 44 +- tests/data/environment.json | 33 + tests/data/{get-flags.json => flags.json} | 11 +- ...et-flag-for-specific-feature-disabled.json | 17 - ...get-flag-for-specific-feature-enabled.json | 17 - tests/data/get-identity-flags-with-trait.json | 27 - .../get-identity-flags-without-trait.json | 22 - .../data/get-value-for-specific-feature.json | 17 - tests/data/identities.json | 29 + tests/data/not-found.json | 3 - tests/test_analytics.py | 2 +- tests/test_flagsmith.py | 349 ++++++--- tests/test_polling_manager.py | 37 + 36 files changed, 1622 insertions(+), 521 deletions(-) create mode 100644 .flake8 create mode 100644 .github/workflows/publish.yml create mode 100644 example/.env.template create mode 100644 example/app.py delete mode 100644 example/example.py create mode 100644 example/requirements.txt create mode 100644 example/templates/home.html create mode 100644 flagsmith/exceptions.py create mode 100644 flagsmith/models.py create mode 100644 flagsmith/polling_manager.py create mode 100644 flagsmith/utils/__init__.py create mode 100644 flagsmith/utils/identities.py create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements-dev.txt delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py create mode 100644 tests/data/environment.json rename tests/data/{get-flags.json => flags.json} (60%) delete mode 100644 tests/data/get-flag-for-specific-feature-disabled.json delete mode 100644 tests/data/get-flag-for-specific-feature-enabled.json delete mode 100644 tests/data/get-identity-flags-with-trait.json delete mode 100644 tests/data/get-identity-flags-without-trait.json delete mode 100644 tests/data/get-value-for-specific-feature.json create mode 100644 tests/data/identities.json delete mode 100644 tests/data/not-found.json create mode 100644 tests/test_polling_manager.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..fbfd088 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 120 +max-complexity = 10 +exclude = .git,__pycache__,.venv diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..8c8324f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,24 @@ +name: Publish Pypi Package + +on: + push: + tags: + - '*' + +jobs: + package: + runs-on: ubuntu-latest + name: Publish Pypi Package + + steps: + - name: Cloning repo + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Build and publish to pypi + uses: JRubics/poetry-publish@v1.10 + with: + pypi_token: ${{ secrets.PYPI_API_TOKEN }} + ignore_dev_requirements: "yes" + build_format: "sdist" diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 93d8009..47f38d0 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,8 +1,7 @@ -name: Pytest and Black formatting +name: Formatting and Tests on: - pull_request - - push jobs: test: @@ -12,7 +11,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - name: Cloning repo @@ -28,11 +27,14 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-dev.txt + pip install poetry + poetry install - name: Check Formatting - run: black --check . + run: | + poetry run black --check . + poetry run flake8 . + poetry run isort --check . - name: Run Tests - run: | - pytest \ No newline at end of file + run: poetry run pytest diff --git a/example/.env.template b/example/.env.template new file mode 100644 index 0000000..7af4488 --- /dev/null +++ b/example/.env.template @@ -0,0 +1,2 @@ +FLASK_APP=app +FLAGSMITH_ENVIRONMENT_KEY= \ No newline at end of file diff --git a/example/app.py b/example/app.py new file mode 100644 index 0000000..8d0db39 --- /dev/null +++ b/example/app.py @@ -0,0 +1,55 @@ +import json +import os + +from flask import Flask, render_template, request + +from flagsmith import Flagsmith +from flagsmith.models import DefaultFlag + +app = Flask(__name__) + +flagsmith = Flagsmith( + environment_key=os.environ.get("FLAGSMITH_ENVIRONMENT_KEY"), + defaults=[ + # Set a default flag which will be used if the "secret_button" + # feature is not returned by the API + DefaultFlag( + enabled=False, + value=json.dumps({"colour": "#b8b8b8"}), + feature_name="secret_button", + ) + ], +) + + +@app.route("/", methods=["GET", "POST"]) +def home(): + if request.args: + identifier = request.args["identifier"] + + trait_key = request.args.get("trait-key") + trait_value = request.args.get("trait-value") + traits = {trait_key: trait_value} if trait_key else None + + # Get the flags for an identity, including the provided trait which will be + # persisted to the API for future requests. + identity_flags = flagsmith.get_identity_flags( + identifier=identifier, traits=traits + ) + show_button = identity_flags.is_feature_enabled("secret_button") + button_data = json.loads(identity_flags.get_feature_value("secret_button")) + return render_template( + "home.html", + show_button=show_button, + button_colour=button_data["colour"], + identifier=identifier, + ) + + # Get the default flags for the current environment + flags = flagsmith.get_environment_flags() + show_button = flags.is_feature_enabled("secret_button") + button_data = json.loads(flags.get_feature_value("secret_button")) + + return render_template( + "home.html", show_button=show_button, button_colour=button_data["colour"] + ) diff --git a/example/example.py b/example/example.py deleted file mode 100644 index e8e9c11..0000000 --- a/example/example.py +++ /dev/null @@ -1,58 +0,0 @@ -import json - -from flagsmith import Flagsmith - -api_key = input("Please provide an environment api key: ") - -flagsmith = Flagsmith(environment_id=api_key) - -identifier = input("Please provide an example identity: ") -feature_name = input("Please provide an example feature name: ") - -print_get_flags = input("Print result of get_flags for environment? (y/n) ") -if print_get_flags.lower() == "y": - print(json.dumps(flagsmith.get_flags(), indent=2)) - -print_get_flags_with_identity = input("Print result of get_flags with identity? (y/n) ") -if print_get_flags_with_identity.lower() == "y": - print(json.dumps(flagsmith.get_flags(identifier), indent=2)) - -print_get_flags_for_user = input("Print result of get_flags_for_user? (y/n) ") -if print_get_flags_for_user.lower() == "y": - print(json.dumps(flagsmith.get_flags_for_user(identifier), indent=2)) - -print_get_value_of_feature_for_environment = input( - "Print result of get_value for environment? (y/n) " -) -if print_get_value_of_feature_for_environment.lower() == "y": - print(flagsmith.get_value(feature_name)) - -print_get_value_of_feature_for_environment = input( - "Print result of get_value for identity? (y/n) " -) -if print_get_value_of_feature_for_environment.lower() == "y": - print(flagsmith.get_value(feature_name, identity=identifier)) - -print_result_of_has_feature = input("Print result of has feature? (y/n) ") -if print_result_of_has_feature.lower() == "y": - print(flagsmith.has_feature(feature_name)) - -print_result_of_feature_enabled_for_environment = input( - "Print result of feature_enabled for environment? (y/n) " -) -if print_result_of_feature_enabled_for_environment.lower() == "y": - print(flagsmith.feature_enabled(feature_name)) - -print_result_of_feature_enabled_for_identity = input( - "Print result of feature_enabled for identity? (y/n) " -) -if print_result_of_feature_enabled_for_identity.lower() == "y": - print(flagsmith.feature_enabled(feature_name, identity=identifier)) - -set_trait = input("Would you like to test traits? (y/n) ") -if set_trait.lower() == "y": - trait_key = input("Trait key: ") - trait_value = input("Trait value: ") - flagsmith.set_trait(trait_key, trait_value, identifier) - print("Trait set successfully") - print("Result from get_trait is %s" % flagsmith.get_trait(trait_key, identifier)) diff --git a/example/readme.md b/example/readme.md index 2a2300c..7ac5c16 100644 --- a/example/readme.md +++ b/example/readme.md @@ -1,19 +1,22 @@ # Flagsmith Basic Python Example -To use this basic example, you'll need to first configure a project with at least one feature in Flagsmith. +This directory contains a basic Flask application which utilises Flagsmith. To run the example application, you'll +need to go through the following steps: -Once you've done this, you'll then need to install the latest version of the Flagsmith package by running: +1. Create an account, organisation and project on [Flagsmith](https://flagsmith.com) +2. Create a feature in the project called "secret_button" +3. Give the feature a value using the json editor as follows: -```bash -pip install flagsmith +```json +{"colour": "#ababab"} ``` -Then you can run: +4. Create a .env file from the template located in this directory with the environment key of one of the environments +in flagsmith (This can be found on the 'settings' page accessed from the menu on the left under the chosen environment.) +5. From a terminal window, export those environment variables (either manually or by using `export $(cat .env)`) +6. Run the app using `flask run` +7. Browse to http://localhost:5000 -```bash -python example.py -``` - -The script will grab some information from you such as the environment key to test with, an identifier and a feature -name. Once you've inputted those, the script will run you through all of the methods available in the Flagsmith -client and print the result. +Now you can play around with the 'secret_button' feature in flagsmith, turn it on to show it and edit the colour in the +json value to edit the colour of the button. You can also identify as a given user and then update the settings for the +secret button feature for that user in the flagsmith interface to see the affect that has too. diff --git a/example/requirements.txt b/example/requirements.txt new file mode 100644 index 0000000..3731c99 --- /dev/null +++ b/example/requirements.txt @@ -0,0 +1,2 @@ +flask==2.0.2 +flagsmith>=3.0.0 \ No newline at end of file diff --git a/example/templates/home.html b/example/templates/home.html new file mode 100644 index 0000000..4744cf0 --- /dev/null +++ b/example/templates/home.html @@ -0,0 +1,25 @@ + + + + +Flagsmith Example + +

Hello, {{ identifier or 'World' }}.

+{% if show_button %} + +{% endif %} + +

+ +
+

Identify as a user

+
+ +

... with an optional user trait

+
+

+ + +
+ + \ No newline at end of file diff --git a/flagsmith/__init__.py b/flagsmith/__init__.py index b1da5ce..46571d5 100644 --- a/flagsmith/__init__.py +++ b/flagsmith/__init__.py @@ -1 +1 @@ -from .flagsmith import Flagsmith +from .flagsmith import Flagsmith # noqa diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index 5003699..8055631 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -32,7 +32,6 @@ def __init__(self, environment_key: str, base_api_url: str, timeout: int = 3): self._last_flushed = datetime.now() self.analytics_data = {} self.timeout = timeout - super().__init__() def flush(self): """ diff --git a/flagsmith/exceptions.py b/flagsmith/exceptions.py new file mode 100644 index 0000000..c1c3b36 --- /dev/null +++ b/flagsmith/exceptions.py @@ -0,0 +1,6 @@ +class FlagsmithClientError(Exception): + pass + + +class FlagsmithAPIError(FlagsmithClientError): + pass diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 714ef3f..3b444e7 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -1,219 +1,184 @@ import logging +import typing +from json import JSONDecodeError import requests - -from .analytics import AnalyticsProcessor +from flag_engine import engine +from flag_engine.environments.builders import build_environment_model +from flag_engine.environments.models import EnvironmentModel +from flag_engine.identities.models import IdentityModel, TraitModel +from requests.adapters import HTTPAdapter, Retry + +from flagsmith.analytics import AnalyticsProcessor +from flagsmith.exceptions import FlagsmithAPIError, FlagsmithClientError +from flagsmith.models import DefaultFlag, Flags +from flagsmith.polling_manager import EnvironmentDataPollingManager +from flagsmith.utils.identities import generate_identities_data logger = logging.getLogger(__name__) -SERVER_URL = "https://api.flagsmith.com/api/v1/" -FLAGS_ENDPOINT = "flags/" -IDENTITY_ENDPOINT = "identities/" -TRAIT_ENDPOINT = "traits/" +DEFAULT_API_URL = "https://api.flagsmith.com/api/v1/" class Flagsmith: def __init__( - self, environment_id, api=SERVER_URL, custom_headers=None, request_timeout=None + self, + environment_key: str, + api_url: str = DEFAULT_API_URL, + custom_headers: typing.Dict[str, typing.Any] = None, + request_timeout: int = None, + enable_client_side_evaluation: bool = False, + environment_refresh_interval_seconds: int = 60, + retries: Retry = None, + enable_analytics: bool = False, + defaults: typing.List[DefaultFlag] = None, ): - """ - Initialise Flagsmith environment. - - :param environment_id: environment key obtained from the Flagsmith UI - :param api: (optional) api url to override when using self hosted version - :param custom_headers: (optional) dict which will be passed in headers for each api call - :param request_timeout: (optional) request timeout in seconds - """ - - self.environment_id = environment_id - self.api = api - self.flags_endpoint = api + FLAGS_ENDPOINT - self.identities_endpoint = api + IDENTITY_ENDPOINT - self.traits_endpoint = api + TRAIT_ENDPOINT - self.custom_headers = custom_headers or {} - self.request_timeout = request_timeout - self._analytics_processor = AnalyticsProcessor( - environment_id, api, self.request_timeout + self.session = requests.Session() + self.session.headers.update( + **{"X-Environment-Key": environment_key}, **(custom_headers or {}) ) + retries = retries or Retry(total=3, backoff_factor=0.1) - def get_flags(self, identity=None): - """ - Get all flags for the environment or optionally provide an identity within an environment - to get their flags. Will return overridden identity flags where given and fill in the gaps - with the default environment flags. - - :param identity: application's unique identifier for the user to check feature states - :return: list of dictionaries representing feature states for environment / identity - """ - if identity: - data = self._get_flags_response(identity=identity) - else: - data = self._get_flags_response() - - if data: - return data - else: - logger.error("Failed to get flags for environment.") + self.api_url = api_url if api_url.endswith("/") else f"{api_url}/" + self.request_timeout = request_timeout + self.session.mount(self.api_url, HTTPAdapter(max_retries=retries)) + + self.environment_flags_url = f"{self.api_url}flags/" + self.identities_url = f"{self.api_url}identities/" + self.environment_url = f"{self.api_url}environment-document/" + + self._environment = None + if enable_client_side_evaluation: + self.environment_data_polling_manager_thread = ( + EnvironmentDataPollingManager( + main=self, + refresh_interval_seconds=environment_refresh_interval_seconds, + ) + ) + self.environment_data_polling_manager_thread.start() - def get_flags_for_user(self, identity): - """ - Get all flags for a user + self._analytics_processor = ( + AnalyticsProcessor( + environment_key, self.api_url, timeout=self.request_timeout + ) + if enable_analytics + else None + ) - :param identity: application's unique identifier for the user to check feature states - :return: list of dictionaries representing identities feature states for environment - """ - return self.get_flags(identity=identity) + self.defaults = defaults or [] - def has_feature(self, feature_name): + def get_environment_flags(self) -> Flags: """ - Determine if given feature exists for an environment. + Get all the default for flags for the current environment. - :param feature_name: name of feature to test existence of - :return: True if exists, False if not. + :return: Flags object holding all the flags for the current environment. """ - data = self._get_flags_response(feature_name) - if data: - feature_id = data["feature"]["id"] - self._analytics_processor.track_feature(feature_id) - return True - - return False + if self._environment: + return self._get_environment_flags_from_document() + return self._get_environment_flags_from_api() - def feature_enabled(self, feature_name, identity=None): + def get_identity_flags( + self, identifier: str, traits: typing.Dict[str, typing.Any] = None + ) -> Flags: """ - Get enabled state of given feature for an environment. + Get all the flags for the current environment for a given identity. Will also + upsert all traits to the Flagsmith API for future evaluations. Providing a + trait with a value of None will remove the trait from the identity if it exists. - :param feature_name: name of feature to determine if enabled - :param identity: (optional) application's unique identifier for the user to check feature state - :return: True / False if feature exists. None otherwise. + :param identifier: a unique identifier for the identity in the current + environment, e.g. email address, username, uuid + :param traits: a dictionary of traits to add / update on the identity in + Flagsmith, e.g. {"num_orders": 10} + :return: Flags object holding all the flags for the given identity. """ - if not feature_name: - return None + traits = traits or {} + if self._environment: + return self._get_identity_flags_from_document(identifier, traits) + return self._get_identity_flags_from_api(identifier, traits) - data = self._get_flags_response(feature_name, identity) + def update_environment(self): + self._environment = self._get_environment_from_api() - if not data: - return None - - feature_id = data["feature"]["id"] - self._analytics_processor.track_feature(feature_id) - - return data["enabled"] - - def get_value(self, feature_name, identity=None): - """ - Get value of given feature for an environment. - - :param feature_name: name of feature to determine value of - :param identity: (optional) application's unique identifier for the user to check feature state - :return: value of the feature state if feature exists, None otherwise - """ - if not feature_name: - return None + def _get_environment_from_api(self) -> EnvironmentModel: + environment_data = self._get_json_response(self.environment_url, method="GET") + return build_environment_model(environment_data) - data = self._get_flags_response(feature_name, identity) - - if not data: - return None - feature_id = data["feature"]["id"] - self._analytics_processor.track_feature(feature_id) - return data["feature_state_value"] - - def get_trait(self, trait_key, identity): - """ - Get value of given trait for the identity of an environment. - - :param trait_key: key of trait to determine value of (must match 'ID' on flagsmith.com) - :param identity: application's unique identifier for the user to check feature state - :return: Trait value. None otherwise. - """ - if not all([trait_key, identity]): - return None - - data = self._get_flags_response(identity=identity, feature_name=None) - - traits = data["traits"] - for trait in traits: - if trait.get("trait_key") == trait_key: - return trait.get("trait_value") + def _get_environment_flags_from_document(self) -> Flags: + return Flags.from_feature_state_models( + feature_states=engine.get_environment_feature_states(self._environment), + analytics_processor=self._analytics_processor, + defaults=self.defaults, + ) - def set_trait(self, trait_key, trait_value, identity): - """ - Set value of given trait for the identity of an environment. Note that this will lazily create - a new trait if the trait_key has not been seen before for this identity + def _get_identity_flags_from_document( + self, identifier: str, traits: typing.Dict[str, typing.Any] + ) -> Flags: + identity_model = self._build_identity_model(identifier, **traits) + feature_states = engine.get_identity_feature_states( + self._environment, identity_model + ) + return Flags.from_feature_state_models( + feature_states=feature_states, + analytics_processor=self._analytics_processor, + identity_id=identity_model.composite_key, + defaults=self.defaults, + ) - :param trait_key: key of trait - :param trait_value: value of trait - :param identity: application's unique identifier for the user to check feature state - """ - values = [trait_key, trait_value, identity] - if None in values or "" in values: - return None - - payload = { - "identity": {"identifier": identity}, - "trait_key": trait_key, - "trait_value": trait_value, - } - - requests.post( - self.traits_endpoint, - json=payload, - headers=self._generate_header_content(self.custom_headers), - timeout=self.request_timeout, + def _get_environment_flags_from_api(self) -> Flags: + api_flags = self._get_json_response( + url=self.environment_flags_url, method="GET" ) - def _get_flags_response(self, feature_name=None, identity=None): - """ - Private helper method to hit the flags endpoint + return Flags.from_api_flags( + api_flags=api_flags, + analytics_processor=self._analytics_processor, + defaults=self.defaults, + ) - :param feature_name: name of feature to determine value of (must match 'ID' on flagsmith.com) - :param identity: (optional) application's unique identifier for the user to check feature state - :return: data returned by API if successful, None if not. - """ - params = {"feature": feature_name} if feature_name else {} + def _get_identity_flags_from_api( + self, identifier: str, traits: typing.Dict[str, typing.Any] + ) -> Flags: + data = generate_identities_data(identifier, traits) + json_response = self._get_json_response( + url=self.identities_url, method="POST", body=data + ) + return Flags.from_api_flags( + api_flags=json_response["flags"], + analytics_processor=self._analytics_processor, + defaults=self.defaults, + ) + def _get_json_response(self, url: str, method: str, body: dict = None): try: - if identity: - params["identifier"] = identity - response = requests.get( - self.identities_endpoint, - params=params, - headers=self._generate_header_content(self.custom_headers), - timeout=self.request_timeout, - ) - else: - response = requests.get( - self.flags_endpoint, - params=params, - headers=self._generate_header_content(self.custom_headers), - timeout=self.request_timeout, + request_method = getattr(self.session, method.lower()) + response = request_method(url, json=body, timeout=self.request_timeout) + if response.status_code != 200: + raise FlagsmithAPIError( + "Invalid request made to Flagsmith API. Response status code: %d", + response.status_code, ) - - if response.status_code == 200: - data = response.json() - if data: - return data - else: - logger.error("API didn't return any data") - return None - else: - return None - - except Exception as e: - logger.error( - "Got error getting response from API. Error message was %s" % e + return response.json() + except (requests.ConnectionError, JSONDecodeError) as e: + raise FlagsmithAPIError( + "Unable to get valid response from Flagsmith API." + ) from e + + def _build_identity_model(self, identifier: str, **traits): + if not self._environment: + raise FlagsmithClientError( + "Unable to build identity model when no local environment present." ) - return None - - def _generate_header_content(self, headers=None): - """ - Generates required header content for accessing API - :param headers: (optional) dictionary of other required header values - :return: dictionary with required environment header appended to it - """ - headers = headers or {} + trait_models = [ + TraitModel(trait_key=key, trait_value=value) + for key, value in traits.items() + ] + return IdentityModel( + identifier=identifier, + environment_api_key=self._environment.api_key, + identity_traits=trait_models, + ) - headers["X-Environment-Key"] = self.environment_id - return headers + def __del__(self): + if hasattr(self, "environment_data_polling_manager_thread"): + self.environment_data_polling_manager_thread.stop() diff --git a/flagsmith/models.py b/flagsmith/models.py new file mode 100644 index 0000000..76c0b0c --- /dev/null +++ b/flagsmith/models.py @@ -0,0 +1,136 @@ +import typing +from dataclasses import dataclass + +from flag_engine.features.models import FeatureStateModel + +from flagsmith.analytics import AnalyticsProcessor +from flagsmith.exceptions import FlagsmithClientError + + +@dataclass +class BaseFlag: + enabled: bool + value: typing.Union[str, int, float, bool] + feature_name: str + + +@dataclass +class DefaultFlag(BaseFlag): + is_default = True + + +@dataclass +class Flag(BaseFlag): + feature_id: int + is_default = False + + @classmethod + def from_feature_state_model( + cls, + feature_state_model: FeatureStateModel, + identity_id: typing.Union[str, int] = None, + ) -> "Flag": + return Flag( + enabled=feature_state_model.enabled, + value=feature_state_model.get_value(identity_id=identity_id), + feature_name=feature_state_model.feature.name, + feature_id=feature_state_model.feature.id, + ) + + @classmethod + def from_api_flag(cls, flag_data: dict) -> "Flag": + return Flag( + enabled=flag_data["enabled"], + value=flag_data["feature_state_value"], + feature_name=flag_data["feature"]["name"], + feature_id=flag_data["feature"]["id"], + ) + + +@dataclass +class Flags: + flags: typing.Dict[str, BaseFlag] + _analytics_processor: AnalyticsProcessor = None + + @classmethod + def from_feature_state_models( + cls, + feature_states: typing.List[FeatureStateModel], + analytics_processor: AnalyticsProcessor, + identity_id: typing.Union[str, int] = None, + defaults: typing.List[DefaultFlag] = None, + ) -> "Flags": + flags = { + feature_state.feature.name: Flag.from_feature_state_model( + feature_state, identity_id=identity_id + ) + for feature_state in feature_states + } + + for default in defaults or []: + flags.setdefault(default.feature_name, default) + + return cls(flags=flags, _analytics_processor=analytics_processor) + + @classmethod + def from_api_flags( + cls, + api_flags: typing.List[dict], + analytics_processor: AnalyticsProcessor, + defaults: typing.List[DefaultFlag] = None, + ) -> "Flags": + flags = { + flag_data["feature"]["name"]: Flag.from_api_flag(flag_data) + for flag_data in api_flags + } + + for default in defaults or []: + flags.setdefault(default.feature_name, default) + + return cls(flags=flags, _analytics_processor=analytics_processor) + + def all_flags(self) -> typing.List[BaseFlag]: + """ + Get a list of all Flag objects. + + :return: list of Flag objects. + """ + return list(self.flags.values()) + + def is_feature_enabled(self, feature_name: str) -> bool: + """ + Check whether a given feature is enabled. + + :param feature_name: the name of the feature to check if enabled. + :return: Boolean representing the enabled state of a given feature. + :raises FlagsmithClientError: if feature doesn't exist + """ + return self.get_flag(feature_name).enabled + + def get_feature_value(self, feature_name: str) -> typing.Any: + """ + Get the value of a particular feature. + + :param feature_name: the name of the feature to retrieve the value of. + :return: the value of the given feature. + :raises FlagsmithClientError: if feature doesn't exist + """ + return self.get_flag(feature_name).value + + def get_flag(self, feature_name: str) -> BaseFlag: + """ + Get a specific flag given the feature name. + + :param feature_name: the name of the feature to retrieve the flag for. + :return: Flag object. + :raises FlagsmithClientError: if feature doesn't exist + """ + try: + flag = self.flags[feature_name] + except KeyError: + raise FlagsmithClientError("Feature does not exist: %s" % feature_name) + + if self._analytics_processor and hasattr(flag, "feature_id"): + self._analytics_processor.track_feature(flag.feature_id) + + return flag diff --git a/flagsmith/polling_manager.py b/flagsmith/polling_manager.py new file mode 100644 index 0000000..0aeb512 --- /dev/null +++ b/flagsmith/polling_manager.py @@ -0,0 +1,27 @@ +import threading +import time +import typing + +if typing.TYPE_CHECKING: + from flagsmith import Flagsmith + + +class EnvironmentDataPollingManager(threading.Thread): + def __init__( + self, main: "Flagsmith", refresh_interval_seconds: typing.Union[int, float] = 10 + ): + super(EnvironmentDataPollingManager, self).__init__() + self._stop_event = threading.Event() + self.main = main + self.refresh_interval_seconds = refresh_interval_seconds + + def run(self) -> None: + while not self._stop_event.is_set(): + self.main.update_environment() + time.sleep(self.refresh_interval_seconds) + + def stop(self) -> None: + self._stop_event.set() + + def __del__(self): + self._stop_event.set() diff --git a/flagsmith/utils/__init__.py b/flagsmith/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flagsmith/utils/identities.py b/flagsmith/utils/identities.py new file mode 100644 index 0000000..9c82333 --- /dev/null +++ b/flagsmith/utils/identities.py @@ -0,0 +1,5 @@ +def generate_identities_data(identifier: str, traits: dict = None): + return { + "identifier": identifier, + "traits": [{"trait_key": k, "trait_value": v} for k, v in traits.items()], + } diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..05a0b07 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,741 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "black" +version = "21.12b0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" +tomli = ">=0.2.6,<2.0.0" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.3)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "charset-normalizer" +version = "2.0.10" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "distlib" +version = "0.3.4" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.4.2" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "flagsmith-flag-engine" +version = "1.5.1" +description = "Flag engine for the Flagsmith API." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +marshmallow = ">=3.14.1" + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "identify" +version = "2.4.5" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.2.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "marshmallow" +version = "3.14.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] +lint = ["mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.4.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.17.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-mock" +version = "3.6.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "tox", "pytest-asyncio"] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "requests-futures" +version = "1.0.0" +description = "Asynchronous Python HTTP for Humans." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=1.2.0" + +[[package]] +name = "responses" +version = "0.17.0" +description = "A utility library for mocking out the `requests` Python library." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +requests = ">=2.0" +six = "*" +urllib3 = ">=1.25.10" + +[package.extras] +tests = ["coverage (>=3.7.1,<6.0.0)", "pytest-cov", "pytest-localserver", "flake8", "types-mock", "types-requests", "types-six", "pytest (>=4.6,<5.0)", "pytest (>=4.6)", "mypy"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typed-ast" +version = "1.5.2" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "urllib3" +version = "1.26.8" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.13.0" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + +[[package]] +name = "zipp" +version = "3.7.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = ">=3.7.0,<4" +content-hash = "296ed13a67def18ad7949ad3b9be19670b977cf9a914def96f371e4569116d3e" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +black = [ + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, + {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, +] +click = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +distlib = [ + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, +] +filelock = [ + {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, + {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, +] +flagsmith-flag-engine = [ + {file = "flagsmith-flag-engine-1.5.1.tar.gz", hash = "sha256:5e9b1ca75bc50df68379afb7c39d5d2edc91fcde4bda15b67b7573e4efc9fe45"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +identify = [ + {file = "identify-2.4.5-py2.py3-none-any.whl", hash = "sha256:d27d10099844741c277b45d809bd452db0d70a9b41ea3cd93799ebbbcc6dcb29"}, + {file = "identify-2.4.5.tar.gz", hash = "sha256:d11469ff952a4d7fd7f9be520d335dc450f585d474b39b5dfb86a500831ab6c7"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, + {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +marshmallow = [ + {file = "marshmallow-3.14.1-py3-none-any.whl", hash = "sha256:04438610bc6dadbdddb22a4a55bcc7f6f8099e69580b2e67f5a681933a1f4400"}, + {file = "marshmallow-3.14.1.tar.gz", hash = "sha256:4c05c1684e0e97fe779c62b91878f173b937fe097b356cd82f793464f5bc6138"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +pre-commit = [ + {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, + {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +pytest-mock = [ + {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, + {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +requests-futures = [ + {file = "requests-futures-1.0.0.tar.gz", hash = "sha256:35547502bf1958044716a03a2f47092a89efe8f9789ab0c4c528d9c9c30bc148"}, + {file = "requests_futures-1.0.0-py2.py3-none-any.whl", hash = "sha256:633804c773b960cef009efe2a5585483443c6eac3c39cc64beba2884013bcdd9"}, +] +responses = [ + {file = "responses-0.17.0-py2.py3-none-any.whl", hash = "sha256:e4fc472fb7374fb8f84fcefa51c515ca4351f198852b4eb7fc88223780b472ea"}, + {file = "responses-0.17.0.tar.gz", hash = "sha256:ec675e080d06bf8d1fb5e5a68a1e5cd0df46b09c78230315f650af5e4036bec7"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, +] +typed-ast = [ + {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, + {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, + {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, + {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, + {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, + {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, + {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, + {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, + {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, + {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, + {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, + {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, + {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, + {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, + {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, + {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, + {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, +] +typing-extensions = [ + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, +] +urllib3 = [ + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, +] +virtualenv = [ + {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, + {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, +] +zipp = [ + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3b6a989 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,31 @@ +[tool.poetry] +name = "flagsmith" +version = "3.0.0" +description = "Flagsmith Python SDK" +authors = ["Flagsmith "] +license = "BSD3" +readme = "Readme.md" +keywords = ["feature", "flag", "flagsmith", "remote", "config"] +documentation = "https://docs.flagsmith.com" +packages = [ + {include = "flagsmith"}, +] + +[tool.poetry.dependencies] +python = ">=3.7.0,<4" +requests = "^2.27.1" +requests-futures = "^1.0.0" +flagsmith-flag-engine = "^1.5.1" + +[tool.poetry.dev-dependencies] +pytest = "^6.2.5" +pytest-mock = "^3.6.1" +black = "^21.12b0" +pre-commit = "^2.17.0" +responses = "^0.17.0" +flake8 = "^4.0.1" +isort = "^5.10.1" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 518bcdc..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ --r requirements.txt -pytest==5.1.2 -black -pre-commit diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index abffd05..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests>=2.19.1 -requests-futures==1.0.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0bc5bd0..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = Readme.md diff --git a/setup.py b/setup.py deleted file mode 100644 index f2ec9a7..0000000 --- a/setup.py +++ /dev/null @@ -1,36 +0,0 @@ -from setuptools import setup - -with open("Readme.md", "r") as readme: - long_description = readme.read() - -setup( - name="flagsmith", - version="3.0.0", - packages=["flagsmith"], - description="Flagsmith Python SDK", - long_description=long_description, - long_description_content_type="text/markdown", - author="Bullet Train Ltd", - author_email="supoprt@flagsmith.com", - license="BSD3", - url="https://github.com/Flagsmith/flagsmith-python-client", - keywords=["feature", "flag", "flagsmith", "remote", "config"], - install_requires=[ - "requests>=2.19.1", - ], - classifiers=[ - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.0", - "Programming Language :: Python :: 3.1", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - ], -) diff --git a/tests/conftest.py b/tests/conftest.py index 7f0cc1c..0f86f6a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,52 @@ +import json +import os +import random +import string + import pytest +from flag_engine.environments.builders import build_environment_model +from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor +DATA_DIR = os.path.join(os.path.dirname(__file__), "data") + -@pytest.fixture +@pytest.fixture() def analytics_processor(): return AnalyticsProcessor( environment_key="test_key", base_api_url="http://test_url" ) + + +@pytest.fixture(scope="session") +def api_key(): + return "".join(random.sample(string.ascii_letters, 20)) + + +@pytest.fixture() +def flagsmith(api_key): + return Flagsmith(environment_key=api_key) + + +@pytest.fixture() +def environment_json(): + with open(os.path.join(DATA_DIR, "environment.json"), "rt") as f: + yield f.read() + + +@pytest.fixture() +def environment_model(environment_json): + return build_environment_model(json.loads(environment_json)) + + +@pytest.fixture() +def flags_json(): + with open(os.path.join(DATA_DIR, "flags.json"), "rt") as f: + yield f.read() + + +@pytest.fixture() +def identities_json(): + with open(os.path.join(DATA_DIR, "identities.json"), "rt") as f: + yield f.read() diff --git a/tests/data/environment.json b/tests/data/environment.json new file mode 100644 index 0000000..d872ff1 --- /dev/null +++ b/tests/data/environment.json @@ -0,0 +1,33 @@ +{ + "api_key": "B62qaMZNwfiqT76p38ggrQ", + "project": { + "name": "Test project", + "organisation": { + "feature_analytics": false, + "name": "Test Org", + "id": 1, + "persist_trait_data": true, + "stop_serving_flags": false + }, + "id": 1, + "hide_disabled_flags": false, + "segments": [] + }, + "segment_overrides": [], + "id": 1, + "feature_states": [ + { + "multivariate_feature_state_values": [], + "feature_state_value": "some-value", + "id": 1, + "featurestate_uuid": "40eb539d-3713-4720-bbd4-829dbef10d51", + "feature": { + "name": "some_feature", + "type": "STANDARD", + "id": 1 + }, + "segment_id": null, + "enabled": true + } + ] +} \ No newline at end of file diff --git a/tests/data/get-flags.json b/tests/data/flags.json similarity index 60% rename from tests/data/get-flags.json rename to tests/data/flags.json index 6ffd5c5..cf06066 100644 --- a/tests/data/get-flags.json +++ b/tests/data/flags.json @@ -3,17 +3,18 @@ "id": 1, "feature": { "id": 1, - "name": "test", + "name": "some_feature", "created_date": "2019-08-27T14:53:45.698555Z", "initial_value": null, "description": null, "default_enabled": false, - "type": "FLAG", + "type": "STANDARD", "project": 1 }, - "feature_state_value": null, - "enabled": false, + "feature_state_value": "some-value", + "enabled": true, "environment": 1, - "identity": null + "identity": null, + "feature_segment": null } ] \ No newline at end of file diff --git a/tests/data/get-flag-for-specific-feature-disabled.json b/tests/data/get-flag-for-specific-feature-disabled.json deleted file mode 100644 index 38a8c58..0000000 --- a/tests/data/get-flag-for-specific-feature-disabled.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": 1, - "feature": { - "id": 1, - "name": "test", - "created_date": "2019-09-03T13:00:51.421698Z", - "initial_value": null, - "description": null, - "default_enabled": false, - "type": "FLAG", - "project": 1 - }, - "feature_state_value": null, - "enabled": false, - "environment": 1, - "identity": null -} \ No newline at end of file diff --git a/tests/data/get-flag-for-specific-feature-enabled.json b/tests/data/get-flag-for-specific-feature-enabled.json deleted file mode 100644 index f7f5205..0000000 --- a/tests/data/get-flag-for-specific-feature-enabled.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": 1, - "feature": { - "id": 1, - "name": "test", - "created_date": "2019-09-03T13:00:51.421698Z", - "initial_value": null, - "description": null, - "default_enabled": false, - "type": "FLAG", - "project": 1 - }, - "feature_state_value": null, - "enabled": true, - "environment": 1, - "identity": null -} \ No newline at end of file diff --git a/tests/data/get-identity-flags-with-trait.json b/tests/data/get-identity-flags-with-trait.json deleted file mode 100644 index 11fca91..0000000 --- a/tests/data/get-identity-flags-with-trait.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "flags": [ - { - "id": 1, - "feature": { - "id": 1, - "name": "test", - "created_date": "2019-09-03T13:00:51.421698Z", - "initial_value": null, - "description": null, - "default_enabled": false, - "type": "FLAG", - "project": 1 - }, - "feature_state_value": null, - "enabled": false, - "environment": 1, - "identity": null - } - ], - "traits": [ - { - "trait_key": "trait_key", - "trait_value": "trait_value" - } - ] -} \ No newline at end of file diff --git a/tests/data/get-identity-flags-without-trait.json b/tests/data/get-identity-flags-without-trait.json deleted file mode 100644 index c5d7a15..0000000 --- a/tests/data/get-identity-flags-without-trait.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "flags": [ - { - "id": 1, - "feature": { - "id": 1, - "name": "test", - "created_date": "2019-09-03T13:00:51.421698Z", - "initial_value": null, - "description": null, - "default_enabled": false, - "type": "FLAG", - "project": 1 - }, - "feature_state_value": null, - "enabled": false, - "environment": 1, - "identity": null - } - ], - "traits": [] -} \ No newline at end of file diff --git a/tests/data/get-value-for-specific-feature.json b/tests/data/get-value-for-specific-feature.json deleted file mode 100644 index fd5a871..0000000 --- a/tests/data/get-value-for-specific-feature.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": 1, - "feature": { - "id": 1, - "name": "test", - "created_date": "2019-09-03T13:00:51.421698Z", - "initial_value": null, - "description": null, - "default_enabled": false, - "type": "FLAG", - "project": 1 - }, - "feature_state_value": "Test value", - "enabled": false, - "environment": 1, - "identity": null -} \ No newline at end of file diff --git a/tests/data/identities.json b/tests/data/identities.json new file mode 100644 index 0000000..1d9c679 --- /dev/null +++ b/tests/data/identities.json @@ -0,0 +1,29 @@ +{ + "traits": [ + { + "id": 1, + "trait_key": "some_trait", + "trait_value": "some_value" + } + ], + "flags": [ + { + "id": 1, + "feature": { + "id": 1, + "name": "some_feature", + "created_date": "2019-08-27T14:53:45.698555Z", + "initial_value": null, + "description": null, + "default_enabled": false, + "type": "STANDARD", + "project": 1 + }, + "feature_state_value": "some-value", + "enabled": true, + "environment": 1, + "identity": null, + "feature_segment": null + } + ] +} \ No newline at end of file diff --git a/tests/data/not-found.json b/tests/data/not-found.json deleted file mode 100644 index 50cc092..0000000 --- a/tests/data/not-found.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "detail": "Given feature not found" -} \ No newline at end of file diff --git a/tests/test_analytics.py b/tests/test_analytics.py index d6e70ea..2b3cc68 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta from unittest import mock -from flagsmith.analytics import ANALYTICS_TIMER, AnalyticsProcessor +from flagsmith.analytics import ANALYTICS_TIMER def test_analytics_processor_track_feature_updates_analytics_data(analytics_processor): diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index b19df9d..95d681f 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -1,151 +1,288 @@ import json -import logging -import os -from unittest import TestCase, mock +import uuid + +import pytest +import requests +import responses +from flag_engine.features.models import FeatureModel, FeatureStateModel from flagsmith import Flagsmith +from flagsmith.exceptions import FlagsmithAPIError +from flagsmith.models import DefaultFlag + + +def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, api_key): + # Given + mock_polling_manager = mocker.MagicMock() + mocker.patch( + "flagsmith.flagsmith.EnvironmentDataPollingManager", + return_value=mock_polling_manager, + ) + + # When + Flagsmith(environment_key=api_key, enable_client_side_evaluation=True) + + # Then + mock_polling_manager.start.assert_called_once() + + +@responses.activate() +def test_update_environment_sets_environment( + flagsmith, environment_json, environment_model +): + # Given + responses.add(method="GET", url=flagsmith.environment_url, body=environment_json) + assert flagsmith._environment is None + + # When + flagsmith.update_environment() + + # Then + assert flagsmith._environment is not None + assert flagsmith._environment == environment_model + + +@responses.activate() +def test_get_environment_flags_calls_api_when_no_local_environment( + api_key, flagsmith, flags_json +): + # Given + responses.add(method="GET", url=flagsmith.environment_flags_url, body=flags_json) + + # When + all_flags = flagsmith.get_environment_flags().all_flags() + + # Then + assert len(responses.calls) == 1 + assert responses.calls[0].request.headers["X-Environment-Key"] == api_key -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) + # Taken from hard coded values in tests/data/flags.json + assert all_flags[0].enabled is True + assert all_flags[0].value == "some-value" + assert all_flags[0].feature_name == "some_feature" -TEST_API_URL = "https://test.bullet-train.io/api" -TEST_IDENTIFIER = "test-identity" -TEST_FEATURE = "test-feature" +@responses.activate() +def test_get_environment_flags_uses_local_environment_when_available( + flagsmith, environment_model +): + # Given + flagsmith._environment = environment_model -class MockResponse: - def __init__(self, data, status_code): - self.json_data = json.loads(data) - self.status_code = status_code + # When + all_flags = flagsmith.get_environment_flags().all_flags() - def json(self): - return self.json_data + # Then + assert len(responses.calls) == 0 + assert len(all_flags) == 1 + assert all_flags[0].feature_name == environment_model.feature_states[0].feature.name + assert all_flags[0].enabled == environment_model.feature_states[0].enabled + assert all_flags[0].value == environment_model.feature_states[0].get_value() -def mock_response(filename, *args, status=200, **kwargs): - print("Hit URL %s with params" % args[0], kwargs.get("params")) - dir_path = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(dir_path, filename), "rt") as f: - return MockResponse(f.read(), status) +@responses.activate() +def test_get_identity_flags_calls_api_when_no_local_environment_no_traits( + flagsmith, identities_json +): + # Given + responses.add(method="POST", url=flagsmith.identities_url, body=identities_json) + identifier = "identifier" + # When + identity_flags = flagsmith.get_identity_flags(identifier=identifier).all_flags() -def mocked_get_specific_feature_flag_enabled(*args, **kwargs): - return mock_response( - "data/get-flag-for-specific-feature-enabled.json", *args, **kwargs + # Then + assert responses.calls[0].request.body.decode() == json.dumps( + {"identifier": identifier, "traits": []} ) + # Taken from hard coded values in tests/data/identities.json + assert identity_flags[0].enabled is True + assert identity_flags[0].value == "some-value" + assert identity_flags[0].feature_name == "some_feature" + + +@responses.activate() +def test_get_identity_flags_calls_api_when_no_local_environment_with_traits( + flagsmith, identities_json +): + # Given + responses.add(method="POST", url=flagsmith.identities_url, body=identities_json) + identifier = "identifier" + traits = {"some_trait": "some_value"} + + # When + identity_flags = flagsmith.get_identity_flags(identifier=identifier, traits=traits) + + # Then + assert responses.calls[0].request.body.decode() == json.dumps( + { + "identifier": identifier, + "traits": [{"trait_key": k, "trait_value": v} for k, v in traits.items()], + } + ) + + # Taken from hard coded values in tests/data/identities.json + assert identity_flags.all_flags()[0].enabled is True + assert identity_flags.all_flags()[0].value == "some-value" + assert identity_flags.all_flags()[0].feature_name == "some_feature" + + +@responses.activate() +def test_get_identity_flags_uses_local_environment_when_available( + flagsmith, environment_model, mocker +): + # Given + flagsmith._environment = environment_model + mock_engine = mocker.patch("flagsmith.flagsmith.engine") -def mocked_get_specific_feature_flag_disabled(*args, **kwargs): - return mock_response( - "data/get-flag-for-specific-feature-disabled.json", *args, **kwargs + feature_state = FeatureStateModel( + feature=FeatureModel(id=1, name="some_feature", type="STANDARD"), + enabled=True, + featurestate_uuid=str(uuid.uuid4()), ) + mock_engine.get_identity_feature_states.return_value = [feature_state] + # When + identity_flags = flagsmith.get_identity_flags( + "identifier", traits={"some_trait": "some_value"} + ).all_flags() -def mocked_get_specific_feature_flag_not_found(*args, **kwargs): - return mock_response("data/not-found.json", *args, status=404, **kwargs) + # Then + mock_engine.get_identity_feature_states.assert_called_once() + assert identity_flags[0].enabled is feature_state.enabled + assert identity_flags[0].value == feature_state.get_value() -def mocked_get_value(*args, **kwargs): - return mock_response("data/get-value-for-specific-feature.json", *args, **kwargs) +def test_request_connection_error_raises_flagsmith_api_error(mocker, api_key): + """ + Test the behaviour when session. raises a ConnectionError. Note that this + does not account for the fact that we are using retries. Since this is a standard + library, we leave this untested. It is assumed that, once the retries are exhausted, + the requests library raises requests.ConnectionError. + """ + # Given + mock_session = mocker.MagicMock() + mocker.patch("flagsmith.flagsmith.requests.Session", return_value=mock_session) -def mocked_get_identity_flags_with_trait(*args, **kwargs): - return mock_response("data/get-identity-flags-with-trait.json", *args, **kwargs) + flagsmith = Flagsmith(environment_key=api_key) + mock_session.get.side_effect = requests.ConnectionError -def mocked_get_identity_flags_without_trait(*args, **kwargs): - return mock_response("data/get-identity-flags-without-trait.json", *args, **kwargs) + # When + with pytest.raises(FlagsmithAPIError): + flagsmith.get_environment_flags() + # Then + # expected exception raised -class FlagsmithTestCase(TestCase): - test_environment_key = "test-env-key" - def setUp(self) -> None: - self.bt = Flagsmith(environment_id=self.test_environment_key, api=TEST_API_URL) +@responses.activate() +def test_non_200_response_raises_flagsmith_api_error(flagsmith): + # Given + responses.add(url=flagsmith.environment_flags_url, method="GET", status=400) - @mock.patch( - "flagsmith.flagsmith.requests.get", - side_effect=mocked_get_specific_feature_flag_enabled, - ) - def test_has_feature_returns_true_if_feature_returned(self, mock_get): - # When - result = self.bt.has_feature(TEST_FEATURE) + # When + with pytest.raises(FlagsmithAPIError): + flagsmith.get_environment_flags() - # Then - assert result + # Then + # expected exception raised - @mock.patch( - "flagsmith.flagsmith.requests.get", - side_effect=mocked_get_specific_feature_flag_not_found, - ) - def test_has_feature_returns_false_if_feature_not_returned(self, mock_get): - # When - result = self.bt.has_feature(TEST_FEATURE) - # Then - assert not result +@responses.activate() +def test_default_flag_is_used_when_no_environment_flags_returned(api_key): + # Given + # a default flag + default_flag = DefaultFlag( + enabled=True, value="some-default-value", feature_name="some_feature" + ) + flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) - @mock.patch( - "flagsmith.flagsmith.requests.get", - side_effect=mocked_get_specific_feature_flag_enabled, + # and we mock the API to return an empty list of flags + responses.add( + url=flagsmith.environment_flags_url, method="GET", body=json.dumps([]) ) - def test_feature_enabled_returns_true_if_feature_enabled(self, mock_get): - # When - result = self.bt.feature_enabled(TEST_FEATURE) - # Then - assert result + # When + flags = flagsmith.get_environment_flags() - @mock.patch( - "flagsmith.flagsmith.requests.get", - side_effect=mocked_get_specific_feature_flag_disabled, + # Then + # the data from the default flag is used + flag = flags.get_flag(default_flag.feature_name) + assert flag.enabled == default_flag.enabled + assert flag.value == default_flag.value + assert flag.feature_name == default_flag.feature_name + + +@responses.activate() +def test_default_flag_is_not_used_when_environment_flags_returned(api_key, flags_json): + # Given + # A default flag + default_flag = DefaultFlag( + enabled=True, value="some-default-value", feature_name="some_feature" ) - def test_feature_enabled_returns_true_if_feature_disabled(self, mock_get): - # When - result = self.bt.feature_enabled(TEST_FEATURE) + flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) - # Then - assert not result + # but we mock the API to return an actual value for the same feature + responses.add(url=flagsmith.environment_flags_url, method="GET", body=flags_json) - @mock.patch("flagsmith.flagsmith.requests.get", side_effect=mocked_get_value) - def test_get_value_returns_value_for_environment_if_feature_exists(self, mock_get): - # When - result = self.bt.get_value(TEST_FEATURE) + # When + flags = flagsmith.get_environment_flags() - # Then - assert result == "Test value" + # Then + # the data from the API response is used, not the default flag + flag = flags.get_flag(default_flag.feature_name) + assert flag.value != default_flag.value + assert flag.value == "some-value" # hard coded value in tests/data/flags.json - @mock.patch( - "flagsmith.flagsmith.requests.get", - side_effect=mocked_get_specific_feature_flag_not_found, - ) - def test_get_value_returns_None_for_environment_if_feature_does_not_exist( - self, mock_get - ): - # When - result = self.bt.get_value(TEST_FEATURE) - - # Then - assert result is None - - @mock.patch( - "flagsmith.flagsmith.requests.get", - side_effect=mocked_get_identity_flags_with_trait, + +@responses.activate() +def test_default_flag_is_used_when_no_identity_flags_returned(api_key): + # Given + # a default flag + default_flag = DefaultFlag( + enabled=True, value="some-default-value", feature_name="some_feature" ) - def test_get_trait_returns_trait_value_if_trait_key_exists(self, mock_get): - # When - result = self.bt.get_trait("trait_key", TEST_IDENTIFIER) + flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) - # Then - assert result == "trait_value" + # and we mock the API to return an empty list of flags + response_data = {"flags": [], "traits": []} + responses.add( + url=flagsmith.identities_url, method="POST", body=json.dumps(response_data) + ) - @mock.patch( - "flagsmith.flagsmith.requests.get", - side_effect=mocked_get_identity_flags_without_trait, + # When + flags = flagsmith.get_identity_flags(identifier="identifier") + + # Then + # the data from the default flag is used + flag = flags.get_flag(default_flag.feature_name) + assert flag.enabled == default_flag.enabled + assert flag.value == default_flag.value + assert flag.feature_name == default_flag.feature_name + + +@responses.activate() +def test_default_flag_is_not_used_when_identity_flags_returned( + api_key, identities_json +): + # Given + # A default flag + default_flag = DefaultFlag( + enabled=True, value="some-default-value", feature_name="some_feature" ) - def test_get_trait_returns_None_if_trait_key_does_not_exist(self, mock_get): - # When - result = self.bt.get_trait("trait_key", TEST_IDENTIFIER) + flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) + + # but we mock the API to return an actual value for the same feature + responses.add(url=flagsmith.identities_url, method="POST", body=identities_json) + + # When + flags = flagsmith.get_identity_flags(identifier="identifier") - # Then - assert result is None + # Then + # the data from the API response is used, not the default flag + flag = flags.get_flag(default_flag.feature_name) + assert flag.value != default_flag.value + assert flag.value == "some-value" # hard coded value in tests/data/identities.json diff --git a/tests/test_polling_manager.py b/tests/test_polling_manager.py new file mode 100644 index 0000000..b4185cc --- /dev/null +++ b/tests/test_polling_manager.py @@ -0,0 +1,37 @@ +import time +from unittest import mock + +from flagsmith.polling_manager import EnvironmentDataPollingManager + + +def test_polling_manager_calls_update_environment_on_start(): + # Given + flagsmith = mock.MagicMock() + polling_manager = EnvironmentDataPollingManager( + main=flagsmith, refresh_interval_seconds=0.1 + ) + + # When + polling_manager.start() + + # Then + flagsmith.update_environment.assert_called_once() + polling_manager.stop() + + +def test_polling_manager_calls_update_environment_on_each_refresh(): + # Given + flagsmith = mock.MagicMock() + polling_manager = EnvironmentDataPollingManager( + main=flagsmith, refresh_interval_seconds=0.1 + ) + + # When + polling_manager.start() + time.sleep(0.25) + + # Then + # 3 calls to update_environment should be made, one when the thread starts and 2 + # for each subsequent refresh + assert flagsmith.update_environment.call_count == 3 + polling_manager.stop() From b6a62eda4599d673035ba685fb68307fb169b65d Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Wed, 9 Feb 2022 14:59:39 +0000 Subject: [PATCH 003/121] improvement/naming tweaks (#21) * improvement/naming-tweaks * Tweaks --- .isort.cfg | 2 +- flagsmith/flagsmith.py | 14 ++++++++------ tests/test_flagsmith.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.isort.cfg b/.isort.cfg index 12a52aa..a607a1e 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -3,4 +3,4 @@ use_parentheses=true multi_line_output=3 include_trailing_comma=true line_length=79 -known_third_party = pytest,requests,requests_futures,setuptools +known_third_party = flag_engine,flask,pytest,requests,requests_futures,responses diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 3b444e7..8b1e28d 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -26,8 +26,8 @@ def __init__( environment_key: str, api_url: str = DEFAULT_API_URL, custom_headers: typing.Dict[str, typing.Any] = None, - request_timeout: int = None, - enable_client_side_evaluation: bool = False, + request_timeout_seconds: int = None, + enable_local_evaluation: bool = False, environment_refresh_interval_seconds: int = 60, retries: Retry = None, enable_analytics: bool = False, @@ -40,7 +40,7 @@ def __init__( retries = retries or Retry(total=3, backoff_factor=0.1) self.api_url = api_url if api_url.endswith("/") else f"{api_url}/" - self.request_timeout = request_timeout + self.request_timeout_seconds = request_timeout_seconds self.session.mount(self.api_url, HTTPAdapter(max_retries=retries)) self.environment_flags_url = f"{self.api_url}flags/" @@ -48,7 +48,7 @@ def __init__( self.environment_url = f"{self.api_url}environment-document/" self._environment = None - if enable_client_side_evaluation: + if enable_local_evaluation: self.environment_data_polling_manager_thread = ( EnvironmentDataPollingManager( main=self, @@ -59,7 +59,7 @@ def __init__( self._analytics_processor = ( AnalyticsProcessor( - environment_key, self.api_url, timeout=self.request_timeout + environment_key, self.api_url, timeout=self.request_timeout_seconds ) if enable_analytics else None @@ -151,7 +151,9 @@ def _get_identity_flags_from_api( def _get_json_response(self, url: str, method: str, body: dict = None): try: request_method = getattr(self.session, method.lower()) - response = request_method(url, json=body, timeout=self.request_timeout) + response = request_method( + url, json=body, timeout=self.request_timeout_seconds + ) if response.status_code != 200: raise FlagsmithAPIError( "Invalid request made to Flagsmith API. Response status code: %d", diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 95d681f..b3fff22 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -20,7 +20,7 @@ def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, api_key): ) # When - Flagsmith(environment_key=api_key, enable_client_side_evaluation=True) + Flagsmith(environment_key=api_key, enable_local_evaluation=True) # Then mock_polling_manager.start.assert_called_once() From 660e83228b26a500580096449fc4f76fe5b93d44 Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Wed, 9 Feb 2022 16:49:14 +0000 Subject: [PATCH 004/121] improvement/default-to-edge (#23) --- flagsmith/flagsmith.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 714ef3f..46a81e8 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -6,7 +6,7 @@ logger = logging.getLogger(__name__) -SERVER_URL = "https://api.flagsmith.com/api/v1/" +SERVER_URL = "https://edge.api.flagsmith.com/api/v1/" FLAGS_ENDPOINT = "flags/" IDENTITY_ENDPOINT = "identities/" TRAIT_ENDPOINT = "traits/" From 30d286ecebfe965183bbb70e39419807c85be1bb Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Thu, 10 Feb 2022 11:10:50 +0000 Subject: [PATCH 005/121] Update License --- License => LICENSE | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) rename License => LICENSE (54%) diff --git a/License b/LICENSE similarity index 54% rename from License rename to LICENSE index e57593a..1608283 100644 --- a/License +++ b/LICENSE @@ -1,12 +1,11 @@ -Copyright (c) 2017 Solid State Technology Ltd (https://www.solidstategroup.com/) and individual contributors. -All rights reserved. +Copyright 2022 Bullet Train Ltd. A UK company. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the Sentry nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 5b49607f144af40006ecae527a24707464c5e4ba Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Thu, 10 Feb 2022 12:55:25 +0000 Subject: [PATCH 006/121] Refactor default flag logic (#22) * Refactor default flags to use a handler function instead of list * Use default handler if API request fails * Add more docstring * Added gitignore for direnv Co-authored-by: Ben Rometsch --- .gitignore | 1 + .isort.cfg | 2 +- example/app.py | 24 +++++++---- flagsmith/flagsmith.py | 88 ++++++++++++++++++++++++++++---------- flagsmith/models.py | 48 ++++++++++++--------- tests/test_flagsmith.py | 94 +++++++++++++++++++++++++++++++---------- 6 files changed, 182 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index d518665..aa2b29d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea/ .venv +.direnv/ *.pyc diff --git a/.isort.cfg b/.isort.cfg index a607a1e..76e57bc 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -3,4 +3,4 @@ use_parentheses=true multi_line_output=3 include_trailing_comma=true line_length=79 -known_third_party = flag_engine,flask,pytest,requests,requests_futures,responses +known_third_party = flag_engine,flask,pytest,requests,requests_futures,responses,urllib3 diff --git a/example/app.py b/example/app.py index 8d0db39..034677b 100644 --- a/example/app.py +++ b/example/app.py @@ -8,17 +8,25 @@ app = Flask(__name__) -flagsmith = Flagsmith( - environment_key=os.environ.get("FLAGSMITH_ENVIRONMENT_KEY"), - defaults=[ - # Set a default flag which will be used if the "secret_button" - # feature is not returned by the API - DefaultFlag( + +def default_flag_handler(feature_name: str) -> DefaultFlag: + """ + Function that will be used if the API doesn't respond, or an unknown + feature is requested + """ + + if feature_name == "secret_button": + return DefaultFlag( enabled=False, value=json.dumps({"colour": "#b8b8b8"}), - feature_name="secret_button", ) - ], + + return DefaultFlag(False, None) + + +flagsmith = Flagsmith( + environment_key=os.environ.get("FLAGSMITH_ENVIRONMENT_KEY"), + default_flag_handler=default_flag_handler, ) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 8b1e28d..820e1fe 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -7,7 +7,8 @@ from flag_engine.environments.builders import build_environment_model from flag_engine.environments.models import EnvironmentModel from flag_engine.identities.models import IdentityModel, TraitModel -from requests.adapters import HTTPAdapter, Retry +from requests.adapters import HTTPAdapter +from urllib3 import Retry from flagsmith.analytics import AnalyticsProcessor from flagsmith.exceptions import FlagsmithAPIError, FlagsmithClientError @@ -21,6 +22,20 @@ class Flagsmith: + """A Flagsmith client. + + Provides an interface for interacting with the Flagsmith http API. + + Basic Usage:: + + >>> from flagsmith import Flagsmith + >>> flagsmith = Flagsmith(environment_key="") + >>> environment_flags = flagsmith.get_environment_flags() + >>> feature_enabled = environment_flags.is_feature_enabled("foo") + >>> identity_flags = flagsmith.get_identity_flags("identifier", {"foo": "bar"}) + >>> feature_enabled_for_identity = identity_flags.is_feature_enabled("foo") + """ + def __init__( self, environment_key: str, @@ -31,8 +46,26 @@ def __init__( environment_refresh_interval_seconds: int = 60, retries: Retry = None, enable_analytics: bool = False, - defaults: typing.List[DefaultFlag] = None, + default_flag_handler: typing.Callable[[str], DefaultFlag] = None, ): + """ + :param environment_key: The environment key obtained from Flagsmith interface + :param api_url: Override the URL of the Flagsmith API to communicate with + :param custom_headers: Additional headers to add to requests made to the + Flagsmith API + :param request_timeout_seconds: Number of seconds to wait for a request to + complete before terminating the request + :param enable_local_evaluation: Enables local evaluation of flags + :param environment_refresh_interval_seconds: If using local evaluation, + specify the interval period between refreshes of local environment data + :param retries: a urllib3.Retry object to use on all http requests to the + Flagsmith API + :param enable_analytics: if enabled, sends additional requests to the Flagsmith + API to power flag analytics charts + :param default_flag_handler: callable which will be used in the case where + flags cannot be retrieved from the API or a non existent feature is + requested + """ self.session = requests.Session() self.session.headers.update( **{"X-Environment-Key": environment_key}, **(custom_headers or {}) @@ -65,7 +98,7 @@ def __init__( else None ) - self.defaults = defaults or [] + self.default_flag_handler = default_flag_handler def get_environment_flags(self) -> Flags: """ @@ -107,7 +140,7 @@ def _get_environment_flags_from_document(self) -> Flags: return Flags.from_feature_state_models( feature_states=engine.get_environment_feature_states(self._environment), analytics_processor=self._analytics_processor, - defaults=self.defaults, + default_flag_handler=self.default_flag_handler, ) def _get_identity_flags_from_document( @@ -121,32 +154,41 @@ def _get_identity_flags_from_document( feature_states=feature_states, analytics_processor=self._analytics_processor, identity_id=identity_model.composite_key, - defaults=self.defaults, + default_flag_handler=self.default_flag_handler, ) def _get_environment_flags_from_api(self) -> Flags: - api_flags = self._get_json_response( - url=self.environment_flags_url, method="GET" - ) - - return Flags.from_api_flags( - api_flags=api_flags, - analytics_processor=self._analytics_processor, - defaults=self.defaults, - ) + try: + api_flags = self._get_json_response( + url=self.environment_flags_url, method="GET" + ) + return Flags.from_api_flags( + api_flags=api_flags, + analytics_processor=self._analytics_processor, + default_flag_handler=self.default_flag_handler, + ) + except FlagsmithAPIError: + if self.default_flag_handler: + return Flags(default_flag_handler=self.default_flag_handler) + raise def _get_identity_flags_from_api( self, identifier: str, traits: typing.Dict[str, typing.Any] ) -> Flags: - data = generate_identities_data(identifier, traits) - json_response = self._get_json_response( - url=self.identities_url, method="POST", body=data - ) - return Flags.from_api_flags( - api_flags=json_response["flags"], - analytics_processor=self._analytics_processor, - defaults=self.defaults, - ) + try: + data = generate_identities_data(identifier, traits) + json_response = self._get_json_response( + url=self.identities_url, method="POST", body=data + ) + return Flags.from_api_flags( + api_flags=json_response["flags"], + analytics_processor=self._analytics_processor, + default_flag_handler=self.default_flag_handler, + ) + except FlagsmithAPIError: + if self.default_flag_handler: + return Flags(default_flag_handler=self.default_flag_handler) + raise def _get_json_response(self, url: str, method: str, body: dict = None): try: diff --git a/flagsmith/models.py b/flagsmith/models.py index 76c0b0c..dd987a9 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -1,5 +1,5 @@ import typing -from dataclasses import dataclass +from dataclasses import dataclass, field from flag_engine.features.models import FeatureStateModel @@ -10,19 +10,20 @@ @dataclass class BaseFlag: enabled: bool - value: typing.Union[str, int, float, bool] - feature_name: str + value: typing.Union[str, int, float, bool, type(None)] + is_default: bool -@dataclass class DefaultFlag(BaseFlag): - is_default = True + def __init__(self, *args, **kwargs): + super().__init__(*args, is_default=True, **kwargs) -@dataclass class Flag(BaseFlag): - feature_id: int - is_default = False + def __init__(self, *args, feature_id: int, feature_name: str, **kwargs): + super().__init__(*args, is_default=False, **kwargs) + self.feature_id = feature_id + self.feature_name = feature_name @classmethod def from_feature_state_model( @@ -49,7 +50,8 @@ def from_api_flag(cls, flag_data: dict) -> "Flag": @dataclass class Flags: - flags: typing.Dict[str, BaseFlag] + flags: typing.Dict[str, Flag] = field(default_factory=dict) + default_flag_handler: typing.Callable[[str], DefaultFlag] = None _analytics_processor: AnalyticsProcessor = None @classmethod @@ -57,8 +59,8 @@ def from_feature_state_models( cls, feature_states: typing.List[FeatureStateModel], analytics_processor: AnalyticsProcessor, + default_flag_handler: typing.Callable, identity_id: typing.Union[str, int] = None, - defaults: typing.List[DefaultFlag] = None, ) -> "Flags": flags = { feature_state.feature.name: Flag.from_feature_state_model( @@ -67,29 +69,31 @@ def from_feature_state_models( for feature_state in feature_states } - for default in defaults or []: - flags.setdefault(default.feature_name, default) - - return cls(flags=flags, _analytics_processor=analytics_processor) + return cls( + flags=flags, + default_flag_handler=default_flag_handler, + _analytics_processor=analytics_processor, + ) @classmethod def from_api_flags( cls, api_flags: typing.List[dict], analytics_processor: AnalyticsProcessor, - defaults: typing.List[DefaultFlag] = None, + default_flag_handler: typing.Callable, ) -> "Flags": flags = { flag_data["feature"]["name"]: Flag.from_api_flag(flag_data) for flag_data in api_flags } - for default in defaults or []: - flags.setdefault(default.feature_name, default) - - return cls(flags=flags, _analytics_processor=analytics_processor) + return cls( + flags=flags, + default_flag_handler=default_flag_handler, + _analytics_processor=analytics_processor, + ) - def all_flags(self) -> typing.List[BaseFlag]: + def all_flags(self) -> typing.List[Flag]: """ Get a list of all Flag objects. @@ -122,12 +126,14 @@ def get_flag(self, feature_name: str) -> BaseFlag: Get a specific flag given the feature name. :param feature_name: the name of the feature to retrieve the flag for. - :return: Flag object. + :return: BaseFlag object. :raises FlagsmithClientError: if feature doesn't exist """ try: flag = self.flags[feature_name] except KeyError: + if self.default_flag_handler: + return self.default_flag_handler(feature_name) raise FlagsmithClientError("Feature does not exist: %s" % feature_name) if self._analytics_processor and hasattr(flag, "feature_id"): diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index b3fff22..e2b2670 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -194,11 +194,17 @@ def test_non_200_response_raises_flagsmith_api_error(flagsmith): @responses.activate() def test_default_flag_is_used_when_no_environment_flags_returned(api_key): # Given - # a default flag - default_flag = DefaultFlag( - enabled=True, value="some-default-value", feature_name="some_feature" + feature_name = "some_feature" + + # a default flag and associated handler + default_flag = DefaultFlag(True, "some-default-value") + + def default_flag_handler(feature_name: str) -> DefaultFlag: + return default_flag + + flagsmith = Flagsmith( + environment_key=api_key, default_flag_handler=default_flag_handler ) - flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) # and we mock the API to return an empty list of flags responses.add( @@ -210,20 +216,26 @@ def test_default_flag_is_used_when_no_environment_flags_returned(api_key): # Then # the data from the default flag is used - flag = flags.get_flag(default_flag.feature_name) + flag = flags.get_flag(feature_name) + assert flag.is_default assert flag.enabled == default_flag.enabled assert flag.value == default_flag.value - assert flag.feature_name == default_flag.feature_name @responses.activate() def test_default_flag_is_not_used_when_environment_flags_returned(api_key, flags_json): # Given - # A default flag - default_flag = DefaultFlag( - enabled=True, value="some-default-value", feature_name="some_feature" + feature_name = "some_feature" + + # a default flag and associated handler + default_flag = DefaultFlag(True, "some-default-value") + + def default_flag_handler(feature_name: str) -> DefaultFlag: + return default_flag + + flagsmith = Flagsmith( + environment_key=api_key, default_flag_handler=default_flag_handler ) - flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) # but we mock the API to return an actual value for the same feature responses.add(url=flagsmith.environment_flags_url, method="GET", body=flags_json) @@ -233,7 +245,8 @@ def test_default_flag_is_not_used_when_environment_flags_returned(api_key, flags # Then # the data from the API response is used, not the default flag - flag = flags.get_flag(default_flag.feature_name) + flag = flags.get_flag(feature_name) + assert not flag.is_default assert flag.value != default_flag.value assert flag.value == "some-value" # hard coded value in tests/data/flags.json @@ -241,11 +254,17 @@ def test_default_flag_is_not_used_when_environment_flags_returned(api_key, flags @responses.activate() def test_default_flag_is_used_when_no_identity_flags_returned(api_key): # Given - # a default flag - default_flag = DefaultFlag( - enabled=True, value="some-default-value", feature_name="some_feature" + feature_name = "some_feature" + + # a default flag and associated handler + default_flag = DefaultFlag(True, "some-default-value") + + def default_flag_handler(feature_name: str) -> DefaultFlag: + return default_flag + + flagsmith = Flagsmith( + environment_key=api_key, default_flag_handler=default_flag_handler ) - flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) # and we mock the API to return an empty list of flags response_data = {"flags": [], "traits": []} @@ -258,10 +277,10 @@ def test_default_flag_is_used_when_no_identity_flags_returned(api_key): # Then # the data from the default flag is used - flag = flags.get_flag(default_flag.feature_name) + flag = flags.get_flag(feature_name) + assert flag.is_default assert flag.enabled == default_flag.enabled assert flag.value == default_flag.value - assert flag.feature_name == default_flag.feature_name @responses.activate() @@ -269,11 +288,17 @@ def test_default_flag_is_not_used_when_identity_flags_returned( api_key, identities_json ): # Given - # A default flag - default_flag = DefaultFlag( - enabled=True, value="some-default-value", feature_name="some_feature" + feature_name = "some_feature" + + # a default flag and associated handler + default_flag = DefaultFlag(True, "some-default-value") + + def default_flag_handler(feature_name: str) -> DefaultFlag: + return default_flag + + flagsmith = Flagsmith( + environment_key=api_key, default_flag_handler=default_flag_handler ) - flagsmith = Flagsmith(environment_key=api_key, defaults=[default_flag]) # but we mock the API to return an actual value for the same feature responses.add(url=flagsmith.identities_url, method="POST", body=identities_json) @@ -283,6 +308,31 @@ def test_default_flag_is_not_used_when_identity_flags_returned( # Then # the data from the API response is used, not the default flag - flag = flags.get_flag(default_flag.feature_name) + flag = flags.get_flag(feature_name) + assert not flag.is_default assert flag.value != default_flag.value assert flag.value == "some-value" # hard coded value in tests/data/identities.json + + +def test_default_flags_are_used_if_api_error_and_default_flag_handler_given(mocker): + # Given + # a default flag and associated handler + default_flag = DefaultFlag(True, "some-default-value") + + def default_flag_handler(feature_name: str) -> DefaultFlag: + return default_flag + + # but we mock the request session to raise a ConnectionError + mock_session = mocker.MagicMock() + mocker.patch("flagsmith.flagsmith.requests.Session", return_value=mock_session) + mock_session.get.side_effect = requests.ConnectionError + + flagsmith = Flagsmith( + environment_key="some-key", default_flag_handler=default_flag_handler + ) + + # When + flags = flagsmith.get_environment_flags() + + # Then + assert flags.get_flag("some-feature") == default_flag From 0687c30d66e0a82b9c4cda91927314e87f885d03 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Mon, 25 Apr 2022 14:49:25 +0100 Subject: [PATCH 007/121] Add method for retrieving segments for an identity (#24) --- flagsmith/flagsmith.py | 28 ++++++++++++++++++++++++++-- flagsmith/models.py | 6 ++++++ tests/conftest.py | 27 +++++++++++++++++++++++++++ tests/data/environment.json | 25 ++++++++++++++++++++++++- tests/test_flagsmith.py | 24 ++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 3 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 820e1fe..4df0f72 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -7,12 +7,13 @@ from flag_engine.environments.builders import build_environment_model from flag_engine.environments.models import EnvironmentModel from flag_engine.identities.models import IdentityModel, TraitModel +from flag_engine.segments.evaluator import get_identity_segments from requests.adapters import HTTPAdapter from urllib3 import Retry from flagsmith.analytics import AnalyticsProcessor from flagsmith.exceptions import FlagsmithAPIError, FlagsmithClientError -from flagsmith.models import DefaultFlag, Flags +from flagsmith.models import DefaultFlag, Flags, Segment from flagsmith.polling_manager import EnvironmentDataPollingManager from flagsmith.utils.identities import generate_identities_data @@ -43,7 +44,7 @@ def __init__( custom_headers: typing.Dict[str, typing.Any] = None, request_timeout_seconds: int = None, enable_local_evaluation: bool = False, - environment_refresh_interval_seconds: int = 60, + environment_refresh_interval_seconds: typing.Union[int, float] = 60, retries: Retry = None, enable_analytics: bool = False, default_flag_handler: typing.Callable[[str], DefaultFlag] = None, @@ -129,6 +130,29 @@ def get_identity_flags( return self._get_identity_flags_from_document(identifier, traits) return self._get_identity_flags_from_api(identifier, traits) + def get_identity_segments( + self, identifier: str, traits: typing.Dict[str, typing.Any] = None + ) -> typing.List[Segment]: + """ + Get a list of segments that the given identity is in. + + :param identifier: a unique identifier for the identity in the current + environment, e.g. email address, username, uuid + :param traits: a dictionary of traits to add / update on the identity in + Flagsmith, e.g. {"num_orders": 10} + :return: list of Segment objects that the identity is part of. + """ + + if not self._environment: + raise FlagsmithClientError( + "Local evaluation required to obtain identity segments." + ) + + traits = traits or {} + identity_model = self._build_identity_model(identifier, **traits) + segment_models = get_identity_segments(self._environment, identity_model) + return [Segment(id=sm.id, name=sm.name) for sm in segment_models] + def update_environment(self): self._environment = self._get_environment_from_api() diff --git a/flagsmith/models.py b/flagsmith/models.py index dd987a9..6eb2988 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -140,3 +140,9 @@ def get_flag(self, feature_name: str) -> BaseFlag: self._analytics_processor.track_feature(flag.feature_id) return flag + + +@dataclass +class Segment: + id: int + name: str diff --git a/tests/conftest.py b/tests/conftest.py index 0f86f6a..856945b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,10 +4,12 @@ import string import pytest +import responses from flag_engine.environments.builders import build_environment_model from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor +from flagsmith.flagsmith import DEFAULT_API_URL DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -24,6 +26,11 @@ def api_key(): return "".join(random.sample(string.ascii_letters, 20)) +@pytest.fixture(scope="session") +def server_api_key(): + return "ser.%s" % "".join(random.sample(string.ascii_letters, 20)) + + @pytest.fixture() def flagsmith(api_key): return Flagsmith(environment_key=api_key) @@ -35,6 +42,26 @@ def environment_json(): yield f.read() +@pytest.fixture() +def local_eval_flagsmith(server_api_key, environment_json, mocker): + mock_session = mocker.MagicMock() + mocker.patch("flagsmith.flagsmith.requests.Session", return_value=mock_session) + + mock_environment_document_response = mocker.MagicMock(status_code=200) + mock_environment_document_response.json.return_value = json.loads(environment_json) + mock_session.get.return_value = mock_environment_document_response + + flagsmith = Flagsmith( + environment_key=server_api_key, + enable_local_evaluation=True, + environment_refresh_interval_seconds=0.1, + ) + + yield flagsmith + + flagsmith.__del__() + + @pytest.fixture() def environment_model(environment_json): return build_environment_model(json.loads(environment_json)) diff --git a/tests/data/environment.json b/tests/data/environment.json index d872ff1..aa0e060 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -11,7 +11,30 @@ }, "id": 1, "hide_disabled_flags": false, - "segments": [] + "segments": [ + { + "id": 1, + "name": "Test segment", + "rules": [ + { + "type": "ALL", + "rules": [ + { + "type": "ALL", + "rules": [], + "conditions": [ + { + "operator": "EQUAL", + "property_": "foo", + "value": "bar" + } + ] + } + ] + } + ] + } + ] }, "segment_overrides": [], "id": 1, diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index e2b2670..39eb440 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -336,3 +336,27 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: # Then assert flags.get_flag("some-feature") == default_flag + + +def test_get_identity_segments_no_traits(local_eval_flagsmith, environment_model): + # Given + identifier = "identifier" + + # When + segments = local_eval_flagsmith.get_identity_segments(identifier) + + # Then + assert segments == [] + + +def test_get_identity_segments_with_valid_trait(local_eval_flagsmith, environment_model): + # Given + identifier = "identifier" + traits = {"foo": "bar"} # obtained from data/environment.json + + # When + segments = local_eval_flagsmith.get_identity_segments(identifier, traits) + + # Then + assert len(segments) == 1 + assert segments[0].name == "Test segment" # obtained from data/environment.json From b3a5a4ae22ecd3c2887a4fe511bbd31dbfe1c902 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Mon, 25 Apr 2022 16:51:42 +0100 Subject: [PATCH 008/121] Prevent initialisation with local evaluation without server key (#25) * Prevent initialisation with local evaluation without server key * Fix test * Formatting --- flagsmith/flagsmith.py | 6 ++++++ tests/conftest.py | 2 -- tests/test_flagsmith.py | 13 ++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 4df0f72..3635462 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -83,6 +83,12 @@ def __init__( self._environment = None if enable_local_evaluation: + if not environment_key.startswith("ser."): + raise ValueError( + "In order to use local evaluation, please generate a server key " + "in the environment settings page." + ) + self.environment_data_polling_manager_thread = ( EnvironmentDataPollingManager( main=self, diff --git a/tests/conftest.py b/tests/conftest.py index 856945b..75e0df3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,12 +4,10 @@ import string import pytest -import responses from flag_engine.environments.builders import build_environment_model from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor -from flagsmith.flagsmith import DEFAULT_API_URL DATA_DIR = os.path.join(os.path.dirname(__file__), "data") diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 39eb440..45c566d 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -11,7 +11,7 @@ from flagsmith.models import DefaultFlag -def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, api_key): +def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, server_api_key): # Given mock_polling_manager = mocker.MagicMock() mocker.patch( @@ -20,7 +20,7 @@ def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, api_key): ) # When - Flagsmith(environment_key=api_key, enable_local_evaluation=True) + Flagsmith(environment_key=server_api_key, enable_local_evaluation=True) # Then mock_polling_manager.start.assert_called_once() @@ -349,7 +349,9 @@ def test_get_identity_segments_no_traits(local_eval_flagsmith, environment_model assert segments == [] -def test_get_identity_segments_with_valid_trait(local_eval_flagsmith, environment_model): +def test_get_identity_segments_with_valid_trait( + local_eval_flagsmith, environment_model +): # Given identifier = "identifier" traits = {"foo": "bar"} # obtained from data/environment.json @@ -360,3 +362,8 @@ def test_get_identity_segments_with_valid_trait(local_eval_flagsmith, environmen # Then assert len(segments) == 1 assert segments[0].name == "Test segment" # obtained from data/environment.json + + +def test_local_evaluation_requires_server_key(): + with pytest.raises(ValueError): + Flagsmith(environment_key="not-a-server-key", enable_local_evaluation=True) From c6df92fb706145c89134476fa8c37c8f04b4e3d3 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Mon, 30 May 2022 15:04:45 +0100 Subject: [PATCH 009/121] Update default url (#27) --- flagsmith/flagsmith.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 3635462..c9c5367 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) -DEFAULT_API_URL = "https://api.flagsmith.com/api/v1/" +DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/" class Flagsmith: From ae5c50266d25925aa9551901480623d57a8bfbf9 Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Tue, 14 Jun 2022 08:51:13 +0100 Subject: [PATCH 010/121] Correct docs link in readme --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index b07e206..a87510d 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,7 @@ The SDK for Python applications for [https://www.flagsmith.com/](https://www.fla ## Adding to your project -For full documentation visit [https://docs.flagsmith.com/clients/python/](https://docs.flagsmith.com/clients/python/) +For full documentation visit [https://docs.flagsmith.com/clients/server-side](https://docs.flagsmith.com/clients/server-side). ## Contributing From 09a74f02109ff4a1ca296163e5ab191e8acc1ec4 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 1 Jul 2022 16:20:54 +0100 Subject: [PATCH 011/121] Bump version 3.0.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b6a989..b6a96d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.0.0" +version = "3.0.1" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 1dffb65997020ad4a98c79cbb64e102e515e8339 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Wed, 13 Jul 2022 10:04:44 +0100 Subject: [PATCH 012/121] Use feature name instead of feature id (#29) --- flagsmith/analytics.py | 4 ++-- flagsmith/models.py | 4 ++-- tests/test_analytics.py | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index 8055631..97b596e 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -53,7 +53,7 @@ def flush(self): self.analytics_data.clear() self._last_flushed = datetime.now() - def track_feature(self, feature_id: int): - self.analytics_data[feature_id] = self.analytics_data.get(feature_id, 0) + 1 + def track_feature(self, feature_name: str): + self.analytics_data[feature_name] = self.analytics_data.get(feature_name, 0) + 1 if (datetime.now() - self._last_flushed).seconds > ANALYTICS_TIMER: self.flush() diff --git a/flagsmith/models.py b/flagsmith/models.py index 6eb2988..4284774 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -136,8 +136,8 @@ def get_flag(self, feature_name: str) -> BaseFlag: return self.default_flag_handler(feature_name) raise FlagsmithClientError("Feature does not exist: %s" % feature_name) - if self._analytics_processor and hasattr(flag, "feature_id"): - self._analytics_processor.track_feature(flag.feature_id) + if self._analytics_processor and hasattr(flag, "feature_name"): + self._analytics_processor.track_feature(flag.feature_name) return flag diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 2b3cc68..60126b4 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -7,15 +7,15 @@ def test_analytics_processor_track_feature_updates_analytics_data(analytics_processor): # When - analytics_processor.track_feature(1) - assert analytics_processor.analytics_data[1] == 1 + analytics_processor.track_feature("my_feature") + assert analytics_processor.analytics_data["my_feature"] == 1 - analytics_processor.track_feature(1) - assert analytics_processor.analytics_data[1] == 2 + analytics_processor.track_feature("my_feature") + assert analytics_processor.analytics_data["my_feature"] == 2 def test_analytics_processor_flush_clears_analytics_data(analytics_processor): - analytics_processor.track_feature(1) + analytics_processor.track_feature("my_feature") analytics_processor.flush() assert analytics_processor.analytics_data == {} @@ -26,13 +26,13 @@ def test_analytics_processor_flush_post_request_data_match_ananlytics_data( # Given with mock.patch("flagsmith.analytics.session") as session: # When - analytics_processor.track_feature(1) - analytics_processor.track_feature(2) + analytics_processor.track_feature("my_feature_1") + analytics_processor.track_feature("my_feature_2") analytics_processor.flush() # Then session.post.assert_called() post_call = session.mock_calls[0] - assert {"1": 1, "2": 1} == json.loads(post_call[2]["data"]) + assert {"my_feature_1": 1, "my_feature_2": 1} == json.loads(post_call[2]["data"]) def test_analytics_processor_flush_early_exit_if_analytics_data_is_empty( @@ -57,7 +57,7 @@ def test_analytics_processor_calling_track_feature_calls_flush_when_timer_runs_o seconds=ANALYTICS_TIMER + 1 ) # When - analytics_processor.track_feature(1) + analytics_processor.track_feature("my_feature") # Then session.post.assert_called() From 317a4281ec8cfe4910687be0136aa8d3428612b6 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 14 Oct 2022 15:05:20 +0100 Subject: [PATCH 013/121] Version bump 3.1.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b6a96d6..90f73f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.0.1" +version = "3.1.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From facf3c2e3c796ca6f86af53bf93dc16a87e1ff34 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 14 Oct 2022 15:11:37 +0100 Subject: [PATCH 014/121] Upgrade engine (2.3.0) --- poetry.lock | 66 ++++++++++++++++++++++++++++++++------------------ pyproject.toml | 2 +- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/poetry.lock b/poetry.lock index 05a0b07..d6ec0df 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,10 +15,10 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] [[package]] name = "black" @@ -37,7 +37,7 @@ tomli = ">=0.2.6,<2.0.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = [ {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, - {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, + {version = ">=3.10.0.0,<3.10.0.1 || >3.10.0.1", markers = "python_version >= \"3.10\""}, ] [package.extras] @@ -72,7 +72,7 @@ optional = false python-versions = ">=3.5.0" [package.extras] -unicode_backport = ["unicodedata2"] +unicode-backport = ["unicodedata2"] [[package]] name = "click" @@ -116,7 +116,7 @@ testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-co [[package]] name = "flagsmith-flag-engine" -version = "1.5.1" +version = "2.3.0" description = "Flag engine for the Flagsmith API." category = "main" optional = false @@ -124,6 +124,7 @@ python-versions = "*" [package.dependencies] marshmallow = ">=3.14.1" +semver = "2.13.0" [[package]] name = "flake8" @@ -171,8 +172,8 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] [[package]] name = "iniconfig" @@ -191,10 +192,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] [[package]] name = "marshmallow" @@ -205,9 +206,9 @@ optional = false python-versions = ">=3.6" [package.extras] -dev = ["pytest", "pytz", "simplejson", "mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)", "tox"] -docs = ["sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.7)"] -lint = ["mypy (==0.910)", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "pre-commit (>=2.4,<3.0)"] +dev = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.7)", "sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -366,7 +367,7 @@ python-versions = ">=3.6" pytest = ">=5.0" [package.extras] -dev = ["pre-commit", "tox", "pytest-asyncio"] +dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pyyaml" @@ -392,7 +393,7 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "requests-futures" @@ -419,7 +420,15 @@ six = "*" urllib3 = ">=1.25.10" [package.extras] -tests = ["coverage (>=3.7.1,<6.0.0)", "pytest-cov", "pytest-localserver", "flake8", "types-mock", "types-requests", "types-six", "pytest (>=4.6,<5.0)", "pytest (>=4.6)", "mypy"] +tests = ["coverage (>=3.7.1,<6.0.0)", "flake8", "mypy", "pytest (>=4.6)", "pytest (>=4.6,<5.0)", "pytest-cov", "pytest-localserver", "types-mock", "types-requests", "types-six"] + +[[package]] +name = "semver" +version = "2.13.0" +description = "Python helper for Semantic Versioning (http://semver.org/)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "six" @@ -471,7 +480,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -491,7 +500,7 @@ six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] [[package]] name = "zipp" @@ -502,13 +511,13 @@ optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = ">=3.7.0,<4" -content-hash = "296ed13a67def18ad7949ad3b9be19670b977cf9a914def96f371e4569116d3e" +content-hash = "fb7f38b9c0d2903104ae5c4443c5d3f5812d966f8d8e522c1e80b9b796dc7b59" [metadata.files] atomicwrites = [ @@ -552,7 +561,7 @@ filelock = [ {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, ] flagsmith-flag-engine = [ - {file = "flagsmith-flag-engine-1.5.1.tar.gz", hash = "sha256:5e9b1ca75bc50df68379afb7c39d5d2edc91fcde4bda15b67b7573e4efc9fe45"}, + {file = "flagsmith-flag-engine-2.3.0.tar.gz", hash = "sha256:696d7f260fa7ee931bb6904ca7c0d211c6c961525e8e90aff48af8ade1d2d4f8"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, @@ -646,6 +655,13 @@ pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, @@ -685,6 +701,10 @@ responses = [ {file = "responses-0.17.0-py2.py3-none-any.whl", hash = "sha256:e4fc472fb7374fb8f84fcefa51c515ca4351f198852b4eb7fc88223780b472ea"}, {file = "responses-0.17.0.tar.gz", hash = "sha256:ec675e080d06bf8d1fb5e5a68a1e5cd0df46b09c78230315f650af5e4036bec7"}, ] +semver = [ + {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, + {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, diff --git a/pyproject.toml b/pyproject.toml index 90f73f6..965e344 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ packages = [ python = ">=3.7.0,<4" requests = "^2.27.1" requests-futures = "^1.0.0" -flagsmith-flag-engine = "^1.5.1" +flagsmith-flag-engine = "^2.3.0" [tool.poetry.dev-dependencies] pytest = "^6.2.5" From 4613bc0c55b465f93fb398be19a7c19338458592 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Thu, 12 Jan 2023 14:20:02 +0000 Subject: [PATCH 015/121] Bump version 3.2.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 965e344..52e96bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.1.0" +version = "3.2.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 4e3f1415bbef6b21fd16ebb53a7b68965a7d8fc6 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 13 Jan 2023 10:08:37 +0000 Subject: [PATCH 016/121] Add proxies option to Flagsmith init (#39) --- flagsmith/flagsmith.py | 3 +++ tests/test_flagsmith.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index c9c5367..2f704f6 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -48,6 +48,7 @@ def __init__( retries: Retry = None, enable_analytics: bool = False, default_flag_handler: typing.Callable[[str], DefaultFlag] = None, + proxies: typing.Dict[str, str] = None, ): """ :param environment_key: The environment key obtained from Flagsmith interface @@ -66,11 +67,13 @@ def __init__( :param default_flag_handler: callable which will be used in the case where flags cannot be retrieved from the API or a non existent feature is requested + :param proxies: as per https://requests.readthedocs.io/en/latest/api/#requests.Session.proxies """ self.session = requests.Session() self.session.headers.update( **{"X-Environment-Key": environment_key}, **(custom_headers or {}) ) + self.session.proxies.update(proxies or {}) retries = retries or Retry(total=3, backoff_factor=0.1) self.api_url = api_url if api_url.endswith("/") else f"{api_url}/" diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 45c566d..5162615 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -367,3 +367,14 @@ def test_get_identity_segments_with_valid_trait( def test_local_evaluation_requires_server_key(): with pytest.raises(ValueError): Flagsmith(environment_key="not-a-server-key", enable_local_evaluation=True) + + +def test_initialise_flagsmith_with_proxies(): + # Given + proxies = {"https": "https://my.proxy.com/proxy-me"} + + # When + flagsmith = Flagsmith(environment_key="test-key", proxies=proxies) + + # Then + assert flagsmith.session.proxies == proxies From 72489c5fdd83cd642cf693a89913838970f8605b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 11:35:23 +0100 Subject: [PATCH 017/121] Bump flask from 2.0.2 to 2.2.5 in /example (#44) Bumps [flask](https://github.com/pallets/flask) from 2.0.2 to 2.2.5. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.0.2...2.2.5) --- updated-dependencies: - dependency-name: flask dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- example/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/requirements.txt b/example/requirements.txt index 3731c99..6be4940 100644 --- a/example/requirements.txt +++ b/example/requirements.txt @@ -1,2 +1,2 @@ -flask==2.0.2 +flask==2.2.5 flagsmith>=3.0.0 \ No newline at end of file From 59deb310c82d4cb7ff83cb9ea71590afd43a5c7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 11:35:50 +0100 Subject: [PATCH 018/121] Bump certifi from 2021.10.8 to 2022.12.7 (#36) Bumps [certifi](https://github.com/certifi/python-certifi) from 2021.10.8 to 2022.12.7. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2021.10.08...2022.12.07) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index d6ec0df..3438d2a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,11 +49,11 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "cfgv" @@ -533,8 +533,8 @@ black = [ {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] cfgv = [ {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, From 5c8fa0f1cada33e4810312315e62728d66e6d655 Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Thu, 18 May 2023 11:54:00 +0100 Subject: [PATCH 019/121] improvement/general-housekeeping (#43) * Update versions Reformat * Linting / precommit for yaml Added prettir Fixed up some files * Removed prettierignore and linted some json and html * Remove support for python 3.7 and add 3.11 --- .github/workflows/publish.yml | 34 ++++++------ .github/workflows/pytest.yml | 70 +++++++++++------------ .pre-commit-config.yaml | 9 ++- .prettierrc.json | 21 +++++++ Readme.md | 15 +++-- example/readme.md | 14 ++--- example/templates/home.html | 31 +++++------ tests/data/environment.json | 102 +++++++++++++++++----------------- tests/data/flags.json | 2 +- tests/data/identities.json | 2 +- 10 files changed, 166 insertions(+), 134 deletions(-) create mode 100644 .prettierrc.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8c8324f..1ee6bfd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,24 +1,24 @@ name: Publish Pypi Package on: - push: - tags: - - '*' + push: + tags: + - '*' jobs: - package: - runs-on: ubuntu-latest - name: Publish Pypi Package + package: + runs-on: ubuntu-latest + name: Publish Pypi Package - steps: - - name: Cloning repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 + steps: + - name: Cloning repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 - - name: Build and publish to pypi - uses: JRubics/poetry-publish@v1.10 - with: - pypi_token: ${{ secrets.PYPI_API_TOKEN }} - ignore_dev_requirements: "yes" - build_format: "sdist" + - name: Build and publish to pypi + uses: JRubics/poetry-publish@v1.10 + with: + pypi_token: ${{ secrets.PYPI_API_TOKEN }} + ignore_dev_requirements: 'yes' + build_format: 'sdist' diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 47f38d0..ac2e3c4 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,40 +1,40 @@ name: Formatting and Tests on: - - pull_request + - pull_request jobs: - test: - runs-on: ubuntu-latest - name: Pytest and Black formatting - - strategy: - max-parallel: 4 - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] - - steps: - - name: Cloning repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install poetry - poetry install - - - name: Check Formatting - run: | - poetry run black --check . - poetry run flake8 . - poetry run isort --check . - - - name: Run Tests - run: poetry run pytest + test: + runs-on: ubuntu-latest + name: Pytest and Black formatting + + strategy: + max-parallel: 4 + matrix: + python-version: ['3.8', '3.9', '3.10', '3.11'] + + steps: + - name: Cloning repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install poetry + poetry install + + - name: Check Formatting + run: | + poetry run black --check . + poetry run flake8 . + poetry run isort --check . + + - name: Run Tests + run: poetry run pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a7503d..3f558a5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,4 +12,11 @@ repos: hooks: - id: black language_version: python3 - + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.7.1 + hooks: + - id: prettier diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..d60e431 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,21 @@ +{ + "proseWrap": "always", + "singleQuote": true, + "printWidth": 120, + "trailingComma": "all", + "tabWidth": 4, + "overrides": [ + { + "files": "**/*.md", + "options": { + "tabWidth": 1 + } + }, + { + "files": ["**/*.yml", "**/*.yaml"], + "options": { + "tabWidth": 2 + } + } + ] +} diff --git a/Readme.md b/Readme.md index a87510d..addfa5c 100644 --- a/Readme.md +++ b/Readme.md @@ -2,25 +2,30 @@ # Flagsmith Python SDK -> Flagsmith allows you to manage feature flags and remote config across multiple projects, environments and organisations. +> Flagsmith allows you to manage feature flags and remote config across multiple projects, environments and +> organisations. The SDK for Python applications for [https://www.flagsmith.com/](https://www.flagsmith.com/). ## Adding to your project -For full documentation visit [https://docs.flagsmith.com/clients/server-side](https://docs.flagsmith.com/clients/server-side). +For full documentation visit +[https://docs.flagsmith.com/clients/server-side](https://docs.flagsmith.com/clients/server-side). ## Contributing -Please read [CONTRIBUTING.md](https://gist.github.com/kyle-ssg/c36a03aebe492e45cbd3eefb21cb0486) for details on our code of conduct, and the process for submitting pull requests +Please read [CONTRIBUTING.md](https://gist.github.com/kyle-ssg/c36a03aebe492e45cbd3eefb21cb0486) for details on our code +of conduct, and the process for submitting pull requests ## Getting Help -If you encounter a bug or feature request we would like to hear about it. Before you submit an issue please search existing issues in order to prevent duplicates. +If you encounter a bug or feature request we would like to hear about it. Before you submit an issue please search +existing issues in order to prevent duplicates. ## Get in touch -If you have any questions about our projects you can email support@flagsmith.com. +If you have any questions about our projects you can email +support@flagsmith.com. ## Useful links diff --git a/example/readme.md b/example/readme.md index 7ac5c16..7b61346 100644 --- a/example/readme.md +++ b/example/readme.md @@ -1,22 +1,22 @@ # Flagsmith Basic Python Example -This directory contains a basic Flask application which utilises Flagsmith. To run the example application, you'll -need to go through the following steps: +This directory contains a basic Flask application which utilises Flagsmith. To run the example application, you'll need +to go through the following steps: 1. Create an account, organisation and project on [Flagsmith](https://flagsmith.com) 2. Create a feature in the project called "secret_button" -3. Give the feature a value using the json editor as follows: +3. Give the feature a value using the json editor as follows: ```json -{"colour": "#ababab"} +{ "colour": "#ababab" } ``` -4. Create a .env file from the template located in this directory with the environment key of one of the environments -in flagsmith (This can be found on the 'settings' page accessed from the menu on the left under the chosen environment.) +4. Create a .env file from the template located in this directory with the environment key of one of the environments in + flagsmith (This can be found on the 'settings' page accessed from the menu on the left under the chosen environment.) 5. From a terminal window, export those environment variables (either manually or by using `export $(cat .env)`) 6. Run the app using `flask run` 7. Browse to http://localhost:5000 Now you can play around with the 'secret_button' feature in flagsmith, turn it on to show it and edit the colour in the json value to edit the colour of the button. You can also identify as a given user and then update the settings for the -secret button feature for that user in the flagsmith interface to see the affect that has too. +secret button feature for that user in the flagsmith interface to see the affect that has too. diff --git a/example/templates/home.html b/example/templates/home.html index 4744cf0..23f440d 100644 --- a/example/templates/home.html +++ b/example/templates/home.html @@ -1,25 +1,24 @@ - + Flagsmith Example -

Hello, {{ identifier or 'World' }}.

-{% if show_button %} - -{% endif %} +

Hello, {{ identifier or 'World' }}.

+ {% if show_button %} + + {% endif %} -

+

-
-

Identify as a user

-
+ +

Identify as a user

+
-

... with an optional user trait

-
-

+

... with an optional user trait

+
+

- -
- - \ No newline at end of file + + + diff --git a/tests/data/environment.json b/tests/data/environment.json index aa0e060..22d5235 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -1,56 +1,56 @@ { - "api_key": "B62qaMZNwfiqT76p38ggrQ", - "project": { - "name": "Test project", - "organisation": { - "feature_analytics": false, - "name": "Test Org", - "id": 1, - "persist_trait_data": true, - "stop_serving_flags": false - }, - "id": 1, - "hide_disabled_flags": false, - "segments": [ - { + "api_key": "B62qaMZNwfiqT76p38ggrQ", + "project": { + "name": "Test project", + "organisation": { + "feature_analytics": false, + "name": "Test Org", + "id": 1, + "persist_trait_data": true, + "stop_serving_flags": false + }, "id": 1, - "name": "Test segment", - "rules": [ - { - "type": "ALL", - "rules": [ - { - "type": "ALL", - "rules": [], - "conditions": [ - { - "operator": "EQUAL", - "property_": "foo", - "value": "bar" - } + "hide_disabled_flags": false, + "segments": [ + { + "id": 1, + "name": "Test segment", + "rules": [ + { + "type": "ALL", + "rules": [ + { + "type": "ALL", + "rules": [], + "conditions": [ + { + "operator": "EQUAL", + "property_": "foo", + "value": "bar" + } + ] + } + ] + } ] - } - ] - } + } ] - } + }, + "segment_overrides": [], + "id": 1, + "feature_states": [ + { + "multivariate_feature_state_values": [], + "feature_state_value": "some-value", + "id": 1, + "featurestate_uuid": "40eb539d-3713-4720-bbd4-829dbef10d51", + "feature": { + "name": "some_feature", + "type": "STANDARD", + "id": 1 + }, + "segment_id": null, + "enabled": true + } ] - }, - "segment_overrides": [], - "id": 1, - "feature_states": [ - { - "multivariate_feature_state_values": [], - "feature_state_value": "some-value", - "id": 1, - "featurestate_uuid": "40eb539d-3713-4720-bbd4-829dbef10d51", - "feature": { - "name": "some_feature", - "type": "STANDARD", - "id": 1 - }, - "segment_id": null, - "enabled": true - } - ] -} \ No newline at end of file +} diff --git a/tests/data/flags.json b/tests/data/flags.json index cf06066..a2f226b 100644 --- a/tests/data/flags.json +++ b/tests/data/flags.json @@ -17,4 +17,4 @@ "identity": null, "feature_segment": null } -] \ No newline at end of file +] diff --git a/tests/data/identities.json b/tests/data/identities.json index 1d9c679..c829bfd 100644 --- a/tests/data/identities.json +++ b/tests/data/identities.json @@ -26,4 +26,4 @@ "feature_segment": null } ] -} \ No newline at end of file +} From 13d5a8d2e3e8e1210200aea054bcccef4192ca06 Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Fri, 19 May 2023 10:20:26 +0100 Subject: [PATCH 020/121] Version 3.2.1 (#45) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 52e96bf..ff75252 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.2.0" +version = "3.2.1" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 5ed3c6c3aecc5c2759b8a94d543a2ea4b7b46926 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 7 Jul 2023 13:27:15 +0100 Subject: [PATCH 021/121] Use daemon argument to ensure that polling manager is killed (#47) --- flagsmith/flagsmith.py | 1 + flagsmith/polling_manager.py | 8 ++++++-- tests/conftest.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 2f704f6..b050707 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -96,6 +96,7 @@ def __init__( EnvironmentDataPollingManager( main=self, refresh_interval_seconds=environment_refresh_interval_seconds, + daemon=True, # noqa ) ) self.environment_data_polling_manager_thread.start() diff --git a/flagsmith/polling_manager.py b/flagsmith/polling_manager.py index 0aeb512..00cc028 100644 --- a/flagsmith/polling_manager.py +++ b/flagsmith/polling_manager.py @@ -8,9 +8,13 @@ class EnvironmentDataPollingManager(threading.Thread): def __init__( - self, main: "Flagsmith", refresh_interval_seconds: typing.Union[int, float] = 10 + self, + *args, + main: "Flagsmith", + refresh_interval_seconds: typing.Union[int, float] = 10, + **kwargs ): - super(EnvironmentDataPollingManager, self).__init__() + super(EnvironmentDataPollingManager, self).__init__(*args, **kwargs) self._stop_event = threading.Event() self.main = main self.refresh_interval_seconds = refresh_interval_seconds diff --git a/tests/conftest.py b/tests/conftest.py index 75e0df3..9d3f2c8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -57,7 +57,7 @@ def local_eval_flagsmith(server_api_key, environment_json, mocker): yield flagsmith - flagsmith.__del__() + del flagsmith @pytest.fixture() From 4c7ea264f8d5acb61658282fc81a59b56f66c153 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 7 Jul 2023 13:55:38 +0100 Subject: [PATCH 022/121] Bump version 3.2.2 (#48) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ff75252..cbcc70a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.2.1" +version = "3.2.2" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 459a690c9c82de1892253481194448c28f301c17 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 14 Jul 2023 16:32:03 +0100 Subject: [PATCH 023/121] Update flag-engine (#49) * Update flag-engine * Update black * Add updated_at --- poetry.lock | 925 ++++++++++++++++++++---------------- pyproject.toml | 4 +- tests/data/environment.json | 3 +- 3 files changed, 528 insertions(+), 404 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3438d2a..7c1c2c0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,86 +1,202 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + [[package]] name = "atomicwrites" -version = "1.4.0" +version = "1.4.1" description = "Atomic file writes." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] [[package]] name = "attrs" -version = "21.4.0" +version = "23.1.0" description = "Classes Without Boilerplate" -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] [[package]] name = "black" -version = "21.12b0" +version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" +files = [ + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +] [package.dependencies] -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0,<1" +packaging = ">=22.0" +pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = ">=0.2.6,<2.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = [ - {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, - {version = ">=3.10.0.0,<3.10.0.1 || >3.10.0.1", markers = "python_version >= \"3.10\""}, -] +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.3)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, +] [[package]] name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] [[package]] name = "charset-normalizer" -version = "2.0.10" +version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false -python-versions = ">=3.5.0" - -[package.extras] -unicode-backport = ["unicodedata2"] +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] [[package]] name = "click" -version = "8.0.3" +version = "8.1.5" description = "Composable command line interface toolkit" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"}, + {file = "click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -88,51 +204,66 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" -version = "0.4.4" +version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "distlib" -version = "0.3.4" +version = "0.3.6" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] [[package]] name = "filelock" -version = "3.4.2" +version = "3.12.2" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] [package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] name = "flagsmith-flag-engine" -version = "2.3.0" +version = "4.0.3" description = "Flag engine for the Flagsmith API." -category = "main" optional = false python-versions = "*" +files = [ + {file = "flagsmith-flag-engine-4.0.3.tar.gz", hash = "sha256:adb73fe9ae92e5a38c1b802f790924b7e61760ead33f888fe7582c6d8b204092"}, +] [package.dependencies] -marshmallow = ">=3.14.1" +pydantic = ">=1.10.8,<2" +pydantic-collections = ">=0.4.0,<1" semver = "2.13.0" [[package]] name = "flake8" version = "4.0.1" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] [package.dependencies] importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} @@ -142,30 +273,39 @@ pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "identify" -version = "2.4.5" +version = "2.5.24" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] [package.extras] license = ["ukkonen"] [[package]] name = "idna" -version = "3.3" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] [[package]] name = "importlib-metadata" version = "4.2.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, + {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -177,102 +317,118 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = "*" +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] [[package]] name = "isort" -version = "5.10.1" +version = "5.11.5" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false -python-versions = ">=3.6.1,<4.0" +python-versions = ">=3.7.0" +files = [ + {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, + {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, +] [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] -[[package]] -name = "marshmallow" -version = "3.14.1" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.7)", "sphinx (==4.3.0)", "sphinx-issues (==1.2.0)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["pytest", "pytz", "simplejson"] - [[package]] name = "mccabe" version = "0.6.1" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = "*" +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] [[package]] name = "nodeenv" -version = "1.6.0" +version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false -python-versions = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" [[package]] name = "packaging" -version = "21.3" +version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] [[package]] name = "pathspec" -version = "0.9.0" +version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, +] [[package]] name = "platformdirs" -version = "2.4.1" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" +version = "2.6.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -283,11 +439,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.17.0" +version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] [package.dependencies] cfgv = ">=2.0.0" @@ -295,51 +454,118 @@ identify = ">=1.0.0" importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} nodeenv = ">=0.11.1" pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" +virtualenv = ">=20.10.0" [[package]] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] [[package]] name = "pycodestyle" version = "2.8.0" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] [[package]] -name = "pyflakes" -version = "2.4.0" -description = "passive checker of Python programs" -category = "dev" +name = "pydantic" +version = "1.10.11" +description = "Data validation and settings management using python type hints" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, + {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, + {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, + {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, + {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, + {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, + {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, + {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, + {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, + {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, + {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, + {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, + {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, + {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, + {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, + {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, + {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, + {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, + {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, + {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, + {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, + {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, + {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] [[package]] -name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" -category = "dev" +name = "pydantic-collections" +version = "0.5.0" +description = "Collections of pydantic models" optional = false -python-versions = ">=3.6" +python-versions = "*" +files = [ + {file = "pydantic-collections-0.5.0.tar.gz", hash = "sha256:fdd212205344d499561dc53583d8ef166f83caeee649bd0070b819d4eb4c5c34"}, + {file = "pydantic_collections-0.5.0-py3-none-any.whl", hash = "sha256:4225e749bfa44352f28927fc3f6ba2efefe38997f38c7f22e2a1bd2024d16c8b"}, +] -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +[package.dependencies] +pydantic = ">=1.8.2,<3.0" +typing-extensions = ">=4.7.1" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] [[package]] name = "pytest" version = "6.2.5" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} @@ -357,11 +583,14 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-mock" -version = "3.6.1" +version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] [package.dependencies] pytest = ">=5.0" @@ -373,46 +602,99 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] [[package]] name = "requests" -version = "2.27.1" +version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-futures" -version = "1.0.0" +version = "1.0.1" description = "Asynchronous Python HTTP for Humans." -category = "main" optional = false python-versions = "*" +files = [ + {file = "requests-futures-1.0.1.tar.gz", hash = "sha256:f55a4ef80070e2858e7d1e73123d2bfaeaf25b93fd34384d8ddf148e2b676373"}, + {file = "requests_futures-1.0.1-py2.py3-none-any.whl", hash = "sha256:4a2f5472e9911a79532137d156aa937cd9cd90fec55677f71b2976d1f7a66d38"}, +] [package.dependencies] requests = ">=1.2.0" +[package.extras] +dev = ["black (>=22.3.0)", "build (>=0.7.0)", "isort (>=5.11.4)", "pyflakes (>=2.2.0)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "pytest-network (>=0.0.1)", "readme-renderer[rst] (>=26.0)", "twine (>=3.4.2)"] + [[package]] name = "responses" version = "0.17.0" description = "A utility library for mocking out the `requests` Python library." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "responses-0.17.0-py2.py3-none-any.whl", hash = "sha256:e4fc472fb7374fb8f84fcefa51c515ca4351f198852b4eb7fc88223780b472ea"}, + {file = "responses-0.17.0.tar.gz", hash = "sha256:ec675e080d06bf8d1fb5e5a68a1e5cd0df46b09c78230315f650af5e4036bec7"}, +] [package.dependencies] requests = ">=2.0" @@ -426,77 +708,156 @@ tests = ["coverage (>=3.7.1,<6.0.0)", "flake8", "mypy", "pytest (>=4.6)", "pytes name = "semver" version = "2.13.0" description = "Python helper for Semantic Versioning (http://semver.org/)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, + {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, +] + +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] [[package]] name = "tomli" -version = "1.2.3" +version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "typed-ast" -version = "1.5.2" +version = "1.5.5" description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, + {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, + {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, + {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, + {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, + {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, + {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, + {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, + {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, + {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, +] [[package]] name = "typing-extensions" -version = "4.0.1" -description = "Backported and Experimental Type Hints for Python 3.6+" -category = "dev" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] [[package]] name = "urllib3" -version = "1.26.8" +version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, + {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, +] [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.13.0" +version = "20.16.2" description = "Virtual Python Environment builder" -category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" +files = [ + {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, + {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, +] [package.dependencies] distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} platformdirs = ">=2,<3" -six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] @@ -504,258 +865,20 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", [[package]] name = "zipp" -version = "3.7.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] [package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = ">=3.7.0,<4" -content-hash = "fb7f38b9c0d2903104ae5c4443c5d3f5812d966f8d8e522c1e80b9b796dc7b59" - -[metadata.files] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, -] -black = [ - {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, - {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, - {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, -] -click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -distlib = [ - {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, - {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, -] -filelock = [ - {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, - {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, -] -flagsmith-flag-engine = [ - {file = "flagsmith-flag-engine-2.3.0.tar.gz", hash = "sha256:696d7f260fa7ee931bb6904ca7c0d211c6c961525e8e90aff48af8ade1d2d4f8"}, -] -flake8 = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, -] -identify = [ - {file = "identify-2.4.5-py2.py3-none-any.whl", hash = "sha256:d27d10099844741c277b45d809bd452db0d70a9b41ea3cd93799ebbbcc6dcb29"}, - {file = "identify-2.4.5.tar.gz", hash = "sha256:d11469ff952a4d7fd7f9be520d335dc450f585d474b39b5dfb86a500831ab6c7"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, - {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] -marshmallow = [ - {file = "marshmallow-3.14.1-py3-none-any.whl", hash = "sha256:04438610bc6dadbdddb22a4a55bcc7f6f8099e69580b2e67f5a681933a1f4400"}, - {file = "marshmallow-3.14.1.tar.gz", hash = "sha256:4c05c1684e0e97fe779c62b91878f173b937fe097b356cd82f793464f5bc6138"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nodeenv = [ - {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, - {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] -platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pre-commit = [ - {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, - {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycodestyle = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, -] -pyflakes = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, -] -pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, -] -pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] -pytest-mock = [ - {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, - {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -requests-futures = [ - {file = "requests-futures-1.0.0.tar.gz", hash = "sha256:35547502bf1958044716a03a2f47092a89efe8f9789ab0c4c528d9c9c30bc148"}, - {file = "requests_futures-1.0.0-py2.py3-none-any.whl", hash = "sha256:633804c773b960cef009efe2a5585483443c6eac3c39cc64beba2884013bcdd9"}, -] -responses = [ - {file = "responses-0.17.0-py2.py3-none-any.whl", hash = "sha256:e4fc472fb7374fb8f84fcefa51c515ca4351f198852b4eb7fc88223780b472ea"}, - {file = "responses-0.17.0.tar.gz", hash = "sha256:ec675e080d06bf8d1fb5e5a68a1e5cd0df46b09c78230315f650af5e4036bec7"}, -] -semver = [ - {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, - {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, - {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, -] -typed-ast = [ - {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, - {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, - {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, - {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, - {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, - {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, - {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, - {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, - {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, - {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, -] -typing-extensions = [ - {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, - {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, -] -urllib3 = [ - {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, - {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, -] -virtualenv = [ - {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, - {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, -] -zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, -] +content-hash = "29e3f9103ddbf416b5ef9f4db975ddd11080d442c9f6195c7dad879c7ad646ea" diff --git a/pyproject.toml b/pyproject.toml index cbcc70a..ef94419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,12 +15,12 @@ packages = [ python = ">=3.7.0,<4" requests = "^2.27.1" requests-futures = "^1.0.0" -flagsmith-flag-engine = "^2.3.0" +flagsmith-flag-engine = "^4.0.0" [tool.poetry.dev-dependencies] pytest = "^6.2.5" pytest-mock = "^3.6.1" -black = "^21.12b0" +black = "^23.3.0" pre-commit = "^2.17.0" responses = "^0.17.0" flake8 = "^4.0.1" diff --git a/tests/data/environment.json b/tests/data/environment.json index 22d5235..afe7a44 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -52,5 +52,6 @@ "segment_id": null, "enabled": true } - ] + ], + "updated_at": "2023-07-14 16:12:00.000000" } From e9560d253431a74e76cac97987dbaccad12ae625 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 27 Jul 2023 13:44:11 +0100 Subject: [PATCH 024/121] chore(release): bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ef94419..86fb77c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.2.2" +version = "3.3.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 36fd7e90277db1dee6666aa4c762d51309283e53 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Mon, 31 Jul 2023 09:49:53 +0100 Subject: [PATCH 025/121] Implementation of offline mode (single client class) (#50) * Initial implementation of offline mode (single client class) * Use offline handler if API request fails * Improve test coverage --- flagsmith/flagsmith.py | 116 +++++++++++++++++++++------------ flagsmith/offline_handlers.py | 22 +++++++ tests/test_flagsmith.py | 98 +++++++++++++++++++++++++++- tests/test_offline_handlers.py | 22 +++++++ 4 files changed, 215 insertions(+), 43 deletions(-) create mode 100644 flagsmith/offline_handlers.py create mode 100644 tests/test_offline_handlers.py diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index b050707..226161c 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -14,6 +14,7 @@ from flagsmith.analytics import AnalyticsProcessor from flagsmith.exceptions import FlagsmithAPIError, FlagsmithClientError from flagsmith.models import DefaultFlag, Flags, Segment +from flagsmith.offline_handlers import BaseOfflineHandler from flagsmith.polling_manager import EnvironmentDataPollingManager from flagsmith.utils.identities import generate_identities_data @@ -39,8 +40,8 @@ class Flagsmith: def __init__( self, - environment_key: str, - api_url: str = DEFAULT_API_URL, + environment_key: str = None, + api_url: str = None, custom_headers: typing.Dict[str, typing.Any] = None, request_timeout_seconds: int = None, enable_local_evaluation: bool = False, @@ -49,9 +50,12 @@ def __init__( enable_analytics: bool = False, default_flag_handler: typing.Callable[[str], DefaultFlag] = None, proxies: typing.Dict[str, str] = None, + offline_mode: bool = False, + offline_handler: BaseOfflineHandler = None, ): """ - :param environment_key: The environment key obtained from Flagsmith interface + :param environment_key: The environment key obtained from Flagsmith interface. + Required unless offline_mode is True. :param api_url: Override the URL of the Flagsmith API to communicate with :param custom_headers: Additional headers to add to requests made to the Flagsmith API @@ -65,51 +69,75 @@ def __init__( :param enable_analytics: if enabled, sends additional requests to the Flagsmith API to power flag analytics charts :param default_flag_handler: callable which will be used in the case where - flags cannot be retrieved from the API or a non existent feature is + flags cannot be retrieved from the API or a non-existent feature is requested :param proxies: as per https://requests.readthedocs.io/en/latest/api/#requests.Session.proxies + :param offline_mode: sets the client into offline mode. Relies on offline_handler for + evaluating flags. + :param offline_handler: provide a handler for offline logic. Used to get environment + document from another source when in offline_mode. Works in place of + default_flag_handler if offline_mode is not set and using remote evaluation. """ - self.session = requests.Session() - self.session.headers.update( - **{"X-Environment-Key": environment_key}, **(custom_headers or {}) - ) - self.session.proxies.update(proxies or {}) - retries = retries or Retry(total=3, backoff_factor=0.1) - - self.api_url = api_url if api_url.endswith("/") else f"{api_url}/" - self.request_timeout_seconds = request_timeout_seconds - self.session.mount(self.api_url, HTTPAdapter(max_retries=retries)) - - self.environment_flags_url = f"{self.api_url}flags/" - self.identities_url = f"{self.api_url}identities/" - self.environment_url = f"{self.api_url}environment-document/" + self.offline_mode = offline_mode + self.enable_local_evaluation = enable_local_evaluation + self.offline_handler = offline_handler + self.default_flag_handler = default_flag_handler + self._analytics_processor = None self._environment = None - if enable_local_evaluation: - if not environment_key.startswith("ser."): - raise ValueError( - "In order to use local evaluation, please generate a server key " - "in the environment settings page." - ) - self.environment_data_polling_manager_thread = ( - EnvironmentDataPollingManager( - main=self, - refresh_interval_seconds=environment_refresh_interval_seconds, - daemon=True, # noqa - ) + # argument validation + if offline_mode and not offline_handler: + raise ValueError("offline_handler must be provided to use offline mode.") + elif default_flag_handler and offline_handler: + raise ValueError( + "Cannot use both default_flag_handler and offline_handler." ) - self.environment_data_polling_manager_thread.start() - self._analytics_processor = ( - AnalyticsProcessor( - environment_key, self.api_url, timeout=self.request_timeout_seconds + if self.offline_handler: + self._environment = self.offline_handler.get_environment() + + if not self.offline_mode: + if not environment_key: + raise ValueError("environment_key is required.") + + self.session = requests.Session() + self.session.headers.update( + **{"X-Environment-Key": environment_key}, **(custom_headers or {}) ) - if enable_analytics - else None - ) + self.session.proxies.update(proxies or {}) + retries = retries or Retry(total=3, backoff_factor=0.1) + + api_url = api_url or DEFAULT_API_URL + self.api_url = api_url if api_url.endswith("/") else f"{api_url}/" + + self.request_timeout_seconds = request_timeout_seconds + self.session.mount(self.api_url, HTTPAdapter(max_retries=retries)) + + self.environment_flags_url = f"{self.api_url}flags/" + self.identities_url = f"{self.api_url}identities/" + self.environment_url = f"{self.api_url}environment-document/" + + if self.enable_local_evaluation: + if not environment_key.startswith("ser."): + raise ValueError( + "In order to use local evaluation, please generate a server key " + "in the environment settings page." + ) + + self.environment_data_polling_manager_thread = ( + EnvironmentDataPollingManager( + main=self, + refresh_interval_seconds=environment_refresh_interval_seconds, + daemon=True, # noqa + ) + ) + self.environment_data_polling_manager_thread.start() - self.default_flag_handler = default_flag_handler + if enable_analytics: + self._analytics_processor = AnalyticsProcessor( + environment_key, self.api_url, timeout=self.request_timeout_seconds + ) def get_environment_flags(self) -> Flags: """ @@ -117,7 +145,7 @@ def get_environment_flags(self) -> Flags: :return: Flags object holding all the flags for the current environment. """ - if self._environment: + if (self.offline_mode or self.enable_local_evaluation) and self._environment: return self._get_environment_flags_from_document() return self._get_environment_flags_from_api() @@ -136,7 +164,7 @@ def get_identity_flags( :return: Flags object holding all the flags for the given identity. """ traits = traits or {} - if self._environment: + if (self.offline_mode or self.enable_local_evaluation) and self._environment: return self._get_identity_flags_from_document(identifier, traits) return self._get_identity_flags_from_api(identifier, traits) @@ -202,7 +230,9 @@ def _get_environment_flags_from_api(self) -> Flags: default_flag_handler=self.default_flag_handler, ) except FlagsmithAPIError: - if self.default_flag_handler: + if self.offline_handler: + return self._get_environment_flags_from_document() + elif self.default_flag_handler: return Flags(default_flag_handler=self.default_flag_handler) raise @@ -220,7 +250,9 @@ def _get_identity_flags_from_api( default_flag_handler=self.default_flag_handler, ) except FlagsmithAPIError: - if self.default_flag_handler: + if self.offline_handler: + return self._get_identity_flags_from_document(identifier, traits) + elif self.default_flag_handler: return Flags(default_flag_handler=self.default_flag_handler) raise diff --git a/flagsmith/offline_handlers.py b/flagsmith/offline_handlers.py new file mode 100644 index 0000000..9d90a24 --- /dev/null +++ b/flagsmith/offline_handlers.py @@ -0,0 +1,22 @@ +import json +from abc import ABC, abstractmethod + +from flag_engine.environments.builders import build_environment_model +from flag_engine.environments.models import EnvironmentModel + + +class BaseOfflineHandler(ABC): + @abstractmethod + def get_environment(self) -> EnvironmentModel: + raise NotImplementedError() + + +class LocalFileHandler(BaseOfflineHandler): + def __init__(self, environment_document_path: str): + with open(environment_document_path) as environment_document: + self.environment = build_environment_model( + json.loads(environment_document.read()) + ) + + def get_environment(self) -> EnvironmentModel: + return self.environment diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 5162615..baaa9cb 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -1,4 +1,5 @@ import json +import typing import uuid import pytest @@ -8,7 +9,12 @@ from flagsmith import Flagsmith from flagsmith.exceptions import FlagsmithAPIError -from flagsmith.models import DefaultFlag +from flagsmith.models import DefaultFlag, Flags +from flagsmith.offline_handlers import BaseOfflineHandler + +if typing.TYPE_CHECKING: + from flag_engine.environments.models import EnvironmentModel + from pytest_mock import MockerFixture def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, server_api_key): @@ -68,6 +74,7 @@ def test_get_environment_flags_uses_local_environment_when_available( ): # Given flagsmith._environment = environment_model + flagsmith.enable_local_evaluation = True # When all_flags = flagsmith.get_environment_flags().all_flags() @@ -134,6 +141,7 @@ def test_get_identity_flags_uses_local_environment_when_available( ): # Given flagsmith._environment = environment_model + flagsmith.enable_local_evaluation = True mock_engine = mocker.patch("flagsmith.flagsmith.engine") feature_state = FeatureStateModel( @@ -378,3 +386,91 @@ def test_initialise_flagsmith_with_proxies(): # Then assert flagsmith.session.proxies == proxies + + +def test_offline_mode(environment_model: "EnvironmentModel") -> None: + # Given + class DummyOfflineHandler(BaseOfflineHandler): + def get_environment(self) -> "EnvironmentModel": + return environment_model + + # When + flagsmith = Flagsmith(offline_mode=True, offline_handler=DummyOfflineHandler()) + + # Then + # we can request the flags from the client successfully + environment_flags: Flags = flagsmith.get_environment_flags() + assert environment_flags.is_feature_enabled("some_feature") is True + + identity_flags: Flags = flagsmith.get_identity_flags("identity") + assert identity_flags.is_feature_enabled("some_feature") is True + + +@responses.activate() +def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( + mocker: "MockerFixture", environment_model: "EnvironmentModel" +) -> None: + # Given + api_url = "http://some.flagsmith.com/api/v1/" + mock_offline_handler = mocker.MagicMock(spec=BaseOfflineHandler) + mock_offline_handler.get_environment.return_value = environment_model + + flagsmith = Flagsmith( + environment_key="some-key", + api_url=api_url, + offline_handler=mock_offline_handler, + ) + + responses.add(flagsmith.environment_flags_url, status=500) + responses.add(flagsmith.identities_url, status=500) + + # When + environment_flags = flagsmith.get_environment_flags() + identity_flags = flagsmith.get_identity_flags("identity", traits={}) + + # Then + mock_offline_handler.get_environment.assert_called_once_with() + + assert environment_flags.is_feature_enabled("some_feature") is True + assert environment_flags.get_feature_value("some_feature") == "some-value" + + assert identity_flags.is_feature_enabled("some_feature") is True + assert identity_flags.get_feature_value("some_feature") == "some-value" + + +def test_cannot_use_offline_mode_without_offline_handler(): + with pytest.raises(ValueError) as e: + # When + Flagsmith(offline_mode=True, offline_handler=None) + + # Then + assert ( + e.exconly() + == "ValueError: offline_handler must be provided to use offline mode." + ) + + +def test_cannot_use_default_handler_and_offline_handler(mocker): + # When + with pytest.raises(ValueError) as e: + Flagsmith( + offline_handler=mocker.MagicMock(spec=BaseOfflineHandler), + default_flag_handler=lambda flag_name: DefaultFlag( + enabled=True, value="foo" + ), + ) + + # Then + assert ( + e.exconly() + == "ValueError: Cannot use both default_flag_handler and offline_handler." + ) + + +def test_cannot_create_flagsmith_client_in_remote_evaluation_without_api_key(): + # When + with pytest.raises(ValueError) as e: + Flagsmith() + + # Then + assert e.exconly() == "ValueError: environment_key is required." diff --git a/tests/test_offline_handlers.py b/tests/test_offline_handlers.py new file mode 100644 index 0000000..862f173 --- /dev/null +++ b/tests/test_offline_handlers.py @@ -0,0 +1,22 @@ +from unittest.mock import mock_open, patch + +from flag_engine.environments.models import EnvironmentModel + +from flagsmith.offline_handlers import LocalFileHandler + + +def test_local_file_handler(environment_json): + with patch("builtins.open", mock_open(read_data=environment_json)) as mock_file: + # Given + environment_document_file_path = "/some/path/environment.json" + local_file_handler = LocalFileHandler(environment_document_file_path) + + # When + environment_model = local_file_handler.get_environment() + + # Then + assert isinstance(environment_model, EnvironmentModel) + assert ( + environment_model.api_key == "B62qaMZNwfiqT76p38ggrQ" + ) # hard coded from json file + mock_file.assert_called_once_with(environment_document_file_path) From 0dea69da668620f5f704d9895d63aab83cb06f8d Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Mon, 31 Jul 2023 10:34:52 +0100 Subject: [PATCH 026/121] Bump version 3.4.0 (#53) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 86fb77c..57174aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.3.0" +version = "3.4.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From eaa201a8c5edda15143b274d35a48078a82c7a05 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 8 Aug 2023 12:17:45 +0100 Subject: [PATCH 027/121] Update flag engine (#54) --- poetry.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7c1c2c0..3b5e87e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -241,12 +241,12 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p [[package]] name = "flagsmith-flag-engine" -version = "4.0.3" +version = "4.0.4" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" files = [ - {file = "flagsmith-flag-engine-4.0.3.tar.gz", hash = "sha256:adb73fe9ae92e5a38c1b802f790924b7e61760ead33f888fe7582c6d8b204092"}, + {file = "flagsmith-flag-engine-4.0.4.tar.gz", hash = "sha256:603d4fc021ef94224900c93c8a14038a766ab20e1b51f8726f44e1a307ce9d7e"}, ] [package.dependencies] From 0801a952a7780d1906e2a15a5a93b6dddd29b440 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:27:27 +0100 Subject: [PATCH 028/121] Bump certifi from 2023.5.7 to 2023.7.22 (#56) Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.5.7 to 2023.7.22. - [Commits](https://github.com/certifi/python-certifi/compare/2023.05.07...2023.07.22) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3b5e87e..131ec37 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "atomicwrites" @@ -83,13 +83,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.5.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] From c62665a15d72e73570b03f057626aaab8865e5fd Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Thu, 31 Aug 2023 12:32:37 +0100 Subject: [PATCH 029/121] ci/bump pytest --- poetry.lock | 281 ++++++++++++++++++++++--------------------------- pyproject.toml | 5 +- 2 files changed, 129 insertions(+), 157 deletions(-) diff --git a/poetry.lock b/poetry.lock index 131ec37..4770b1e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,36 +1,5 @@ # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - -[[package]] -name = "attrs" -version = "23.1.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] - [[package]] name = "black" version = "23.3.0" @@ -189,13 +158,13 @@ files = [ [[package]] name = "click" -version = "8.1.5" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"}, - {file = "click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -215,15 +184,29 @@ files = [ [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.12.2" @@ -392,13 +375,13 @@ files = [ [[package]] name = "pathspec" -version = "0.11.1" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, - {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] @@ -456,17 +439,6 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - [[package]] name = "pycodestyle" version = "2.8.0" @@ -480,47 +452,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.11" +version = "1.10.12" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, - {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, - {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, - {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, - {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, - {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, - {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, - {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, - {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, - {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, ] [package.dependencies] @@ -532,13 +504,13 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pydantic-collections" -version = "0.5.0" +version = "0.5.1" description = "Collections of pydantic models" optional = false python-versions = "*" files = [ - {file = "pydantic-collections-0.5.0.tar.gz", hash = "sha256:fdd212205344d499561dc53583d8ef166f83caeee649bd0070b819d4eb4c5c34"}, - {file = "pydantic_collections-0.5.0-py3-none-any.whl", hash = "sha256:4225e749bfa44352f28927fc3f6ba2efefe38997f38c7f22e2a1bd2024d16c8b"}, + {file = "pydantic-collections-0.5.1.tar.gz", hash = "sha256:8b4d2f5a7052dab8d8036cc3d5b013dba20809fd4f43599002a90f40da4653bd"}, + {file = "pydantic_collections-0.5.1-py3-none-any.whl", hash = "sha256:b58f5b17946d997d14f2d92a4d6a0a31b10940dc9e40fcda1ae31bd2d7e62e22"}, ] [package.dependencies] @@ -558,28 +530,26 @@ files = [ [[package]] name = "pytest" -version = "6.2.5" +version = "7.4.0" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-mock" @@ -600,51 +570,61 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] @@ -742,17 +722,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - [[package]] name = "tomli" version = "2.0.1" @@ -827,13 +796,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.3" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"}, - {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"}, + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, ] [package.extras] @@ -881,4 +850,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.7.0,<4" -content-hash = "29e3f9103ddbf416b5ef9f4db975ddd11080d442c9f6195c7dad879c7ad646ea" +content-hash = "884e2836726c33f7f0706f663d1cbe5be4aef1d8431b3f7c5e345edd546a4095" diff --git a/pyproject.toml b/pyproject.toml index 57174aa..27f5277 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ requests-futures = "^1.0.0" flagsmith-flag-engine = "^4.0.0" [tool.poetry.dev-dependencies] -pytest = "^6.2.5" +pytest = "^7.4.0" pytest-mock = "^3.6.1" black = "^23.3.0" pre-commit = "^2.17.0" @@ -26,6 +26,9 @@ responses = "^0.17.0" flake8 = "^4.0.1" isort = "^5.10.1" +[tool.poetry.group.dev.dependencies] +pytest = "^7.4.0" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" From 77c8c2d2d17da133e491ea93264ca72b1d879d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Silva?= Date: Wed, 13 Sep 2023 17:33:50 +0100 Subject: [PATCH 030/121] Ensure polling thread is resilient to errors and exceptions (#60) * Ensure polling thread is resilient to API errors * Ensure polling thread is resilient to request exceptions * Use proper logger * Remove intermediate mocks * Prefer mocker fixture * Remove unecessary return_value with side_effect --------- Co-authored-by: Kim Gustyr --- flagsmith/polling_manager.py | 12 +++++++++++- tests/test_polling_manager.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/flagsmith/polling_manager.py b/flagsmith/polling_manager.py index 00cc028..e922ea8 100644 --- a/flagsmith/polling_manager.py +++ b/flagsmith/polling_manager.py @@ -1,10 +1,17 @@ +import logging import threading import time import typing +import requests + +from flagsmith.exceptions import FlagsmithAPIError + if typing.TYPE_CHECKING: from flagsmith import Flagsmith +logger = logging.getLogger(__name__) + class EnvironmentDataPollingManager(threading.Thread): def __init__( @@ -21,7 +28,10 @@ def __init__( def run(self) -> None: while not self._stop_event.is_set(): - self.main.update_environment() + try: + self.main.update_environment() + except (FlagsmithAPIError, requests.RequestException): + logger.exception("Failed to update environment") time.sleep(self.refresh_interval_seconds) def stop(self) -> None: diff --git a/tests/test_polling_manager.py b/tests/test_polling_manager.py index b4185cc..6cfcffd 100644 --- a/tests/test_polling_manager.py +++ b/tests/test_polling_manager.py @@ -1,6 +1,9 @@ import time from unittest import mock +import requests + +from flagsmith import Flagsmith from flagsmith.polling_manager import EnvironmentDataPollingManager @@ -35,3 +38,35 @@ def test_polling_manager_calls_update_environment_on_each_refresh(): # for each subsequent refresh assert flagsmith.update_environment.call_count == 3 polling_manager.stop() + + +def test_polling_manager_is_resilient_to_api_errors(mocker, server_api_key): + # Given + session_mock = mocker.patch("requests.Session") + session_mock.get.return_value = mock.MagicMock(status_code=500) + flagsmith = Flagsmith( + environment_key=server_api_key, + enable_local_evaluation=True, + environment_refresh_interval_seconds=0.1, + ) + polling_manager = flagsmith.environment_data_polling_manager_thread + + # Then + assert polling_manager.is_alive() + polling_manager.stop() + + +def test_polling_manager_is_resilient_to_request_exceptions(mocker, server_api_key): + # Given + session_mock = mocker.patch("requests.Session") + session_mock.get.side_effect = requests.RequestException() + flagsmith = Flagsmith( + environment_key=server_api_key, + enable_local_evaluation=True, + environment_refresh_interval_seconds=0.1, + ) + polling_manager = flagsmith.environment_data_polling_manager_thread + + # Then + assert polling_manager.is_alive() + polling_manager.stop() From 7721dc7ea92b551438afec6c1228521535063c4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 23:08:36 +0100 Subject: [PATCH 031/121] Bump urllib3 from 2.0.4 to 2.0.7 (#64) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.7. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.4...2.0.7) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4770b1e..ea6557b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -796,13 +796,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] From 96524b93068137b8de9c6f5d28d2a49a50dbc425 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 23 Nov 2023 14:08:37 +0000 Subject: [PATCH 032/121] feat: Bump `flagsmith-flag-engine` to 5.0.0 (#69) --- flagsmith/flagsmith.py | 3 +- flagsmith/offline_handlers.py | 6 +- poetry.lock | 396 ++++++++++++++++++++++------------ pyproject.toml | 8 +- tests/conftest.py | 6 +- 5 files changed, 264 insertions(+), 155 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 226161c..06b41a3 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -4,7 +4,6 @@ import requests from flag_engine import engine -from flag_engine.environments.builders import build_environment_model from flag_engine.environments.models import EnvironmentModel from flag_engine.identities.models import IdentityModel, TraitModel from flag_engine.segments.evaluator import get_identity_segments @@ -196,7 +195,7 @@ def update_environment(self): def _get_environment_from_api(self) -> EnvironmentModel: environment_data = self._get_json_response(self.environment_url, method="GET") - return build_environment_model(environment_data) + return EnvironmentModel.model_validate(environment_data) def _get_environment_flags_from_document(self) -> Flags: return Flags.from_feature_state_models( diff --git a/flagsmith/offline_handlers.py b/flagsmith/offline_handlers.py index 9d90a24..bde1ad0 100644 --- a/flagsmith/offline_handlers.py +++ b/flagsmith/offline_handlers.py @@ -1,7 +1,5 @@ -import json from abc import ABC, abstractmethod -from flag_engine.environments.builders import build_environment_model from flag_engine.environments.models import EnvironmentModel @@ -14,8 +12,8 @@ def get_environment(self) -> EnvironmentModel: class LocalFileHandler(BaseOfflineHandler): def __init__(self, environment_document_path: str): with open(environment_document_path) as environment_document: - self.environment = build_environment_model( - json.loads(environment_document.read()) + self.environment = EnvironmentModel.model_validate_json( + environment_document.read() ) def get_environment(self) -> EnvironmentModel: diff --git a/poetry.lock b/poetry.lock index ea6557b..e9e4411 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,18 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "black" @@ -52,13 +66,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -74,86 +88,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -195,13 +224,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -224,18 +253,18 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p [[package]] name = "flagsmith-flag-engine" -version = "4.0.4" +version = "5.0.0" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" files = [ - {file = "flagsmith-flag-engine-4.0.4.tar.gz", hash = "sha256:603d4fc021ef94224900c93c8a14038a766ab20e1b51f8726f44e1a307ce9d7e"}, + {file = "flagsmith-flag-engine-5.0.0.tar.gz", hash = "sha256:d89a894f8c79da3b5c4688b979e16d038b29bc34c1dbfea48f74be26d60e4ac9"}, ] [package.dependencies] -pydantic = ">=1.10.8,<2" -pydantic-collections = ">=0.4.0,<1" -semver = "2.13.0" +pydantic = ">=2.3.0,<3" +pydantic-collections = ">=0.5.1,<1" +semver = ">=3.0.1" [[package]] name = "flake8" @@ -364,13 +393,13 @@ setuptools = "*" [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -452,55 +481,23 @@ files = [ [[package]] name = "pydantic" -version = "1.10.12" -description = "Data validation and settings management using python type hints" +version = "2.5.2" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"}, + {file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +importlib-metadata = {version = "*", markers = "python_version == \"3.7\""} +pydantic-core = "2.14.5" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-collections" @@ -517,6 +514,123 @@ files = [ pydantic = ">=1.8.2,<3.0" typing-extensions = ">=4.7.1" +[[package]] +name = "pydantic-core" +version = "2.14.5" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"}, + {file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"}, + {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"}, + {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"}, + {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"}, + {file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"}, + {file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"}, + {file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"}, + {file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"}, + {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"}, + {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"}, + {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"}, + {file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"}, + {file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"}, + {file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"}, + {file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"}, + {file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"}, + {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"}, + {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"}, + {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"}, + {file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"}, + {file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"}, + {file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"}, + {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"}, + {file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"}, + {file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"}, + {file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"}, + {file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"}, + {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"}, + {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"}, + {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"}, + {file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"}, + {file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"}, + {file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"}, + {file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"}, + {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"}, + {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"}, + {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"}, + {file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"}, + {file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"}, + {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"}, + {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"}, + {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"}, + {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"}, + {file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pyflakes" version = "2.4.0" @@ -530,13 +644,13 @@ files = [ [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -686,13 +800,13 @@ tests = ["coverage (>=3.7.1,<6.0.0)", "flake8", "mypy", "pytest (>=4.6)", "pytes [[package]] name = "semver" -version = "2.13.0" -description = "Python helper for Semantic Versioning (http://semver.org/)" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7" files = [ - {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, - {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, ] [[package]] @@ -850,4 +964,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.7.0,<4" -content-hash = "884e2836726c33f7f0706f663d1cbe5be4aef1d8431b3f7c5e345edd546a4095" +content-hash = "d513b76114ceb0b46104589ab61cda88138ec8256b904d7aadfd04bc606497b4" diff --git a/pyproject.toml b/pyproject.toml index 27f5277..59003b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,19 @@ [tool.poetry] name = "flagsmith" -version = "3.4.0" +version = "3.5.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" readme = "Readme.md" keywords = ["feature", "flag", "flagsmith", "remote", "config"] documentation = "https://docs.flagsmith.com" -packages = [ - {include = "flagsmith"}, -] +packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] python = ">=3.7.0,<4" requests = "^2.27.1" requests-futures = "^1.0.0" -flagsmith-flag-engine = "^4.0.0" +flagsmith-flag-engine = "^5.0.0" [tool.poetry.dev-dependencies] pytest = "^7.4.0" diff --git a/tests/conftest.py b/tests/conftest.py index 9d3f2c8..b8105a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import string import pytest -from flag_engine.environments.builders import build_environment_model +from flag_engine.environments.models import EnvironmentModel from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor @@ -61,8 +61,8 @@ def local_eval_flagsmith(server_api_key, environment_json, mocker): @pytest.fixture() -def environment_model(environment_json): - return build_environment_model(json.loads(environment_json)) +def environment_model(environment_json: str) -> EnvironmentModel: + return EnvironmentModel.model_validate_json(environment_json) @pytest.fixture() From 5db6d24fb6065f8f8719f5f53c05f6bac6f80f26 Mon Sep 17 00:00:00 2001 From: Ben Miller Date: Tue, 6 Feb 2024 21:32:16 +0000 Subject: [PATCH 033/121] #61 SSE streaming manager (#73) * #61 SSE streaming manager * Remove extraneous import * isort fixes * Self CR * CR changes * Simplify Flagsmith.__init__ to keep flake8 happy * Remove extraneous noqa * Adds typing to new methods and tests * Remove typing.TypeAlias Incompatible with python <= 3.9 * Use Optional as opposed to pipe --- flagsmith/flagsmith.py | 85 +++++++-- flagsmith/streaming_manager.py | 57 ++++++ poetry.lock | 304 +++++++++++++++++--------------- pyproject.toml | 4 +- tests/conftest.py | 18 +- tests/test_flagsmith.py | 42 ++++- tests/test_streaming_manager.py | 177 +++++++++++++++++++ 7 files changed, 533 insertions(+), 154 deletions(-) create mode 100644 flagsmith/streaming_manager.py create mode 100644 tests/test_streaming_manager.py diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 06b41a3..7a807af 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -1,7 +1,9 @@ +import json import logging import typing -from json import JSONDecodeError +from datetime import datetime +import pytz import requests from flag_engine import engine from flag_engine.environments.models import EnvironmentModel @@ -15,11 +17,13 @@ from flagsmith.models import DefaultFlag, Flags, Segment from flagsmith.offline_handlers import BaseOfflineHandler from flagsmith.polling_manager import EnvironmentDataPollingManager +from flagsmith.streaming_manager import EventStreamManager, StreamEvent from flagsmith.utils.identities import generate_identities_data logger = logging.getLogger(__name__) DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/" +DEFAULT_REALTIME_API_URL = "https://realtime.flagsmith.com/" class Flagsmith: @@ -41,6 +45,7 @@ def __init__( self, environment_key: str = None, api_url: str = None, + realtime_api_url: typing.Optional[str] = None, custom_headers: typing.Dict[str, typing.Any] = None, request_timeout_seconds: int = None, enable_local_evaluation: bool = False, @@ -51,11 +56,13 @@ def __init__( proxies: typing.Dict[str, str] = None, offline_mode: bool = False, offline_handler: BaseOfflineHandler = None, + enable_realtime_updates: bool = False, ): """ :param environment_key: The environment key obtained from Flagsmith interface. Required unless offline_mode is True. :param api_url: Override the URL of the Flagsmith API to communicate with + :param realtime_api_url: Override the URL of the Flagsmith real-time API :param custom_headers: Additional headers to add to requests made to the Flagsmith API :param request_timeout_seconds: Number of seconds to wait for a request to @@ -76,12 +83,15 @@ def __init__( :param offline_handler: provide a handler for offline logic. Used to get environment document from another source when in offline_mode. Works in place of default_flag_handler if offline_mode is not set and using remote evaluation. + :param enable_realtime_updates: Use real-time functionality via SSE as opposed to polling the API """ self.offline_mode = offline_mode self.enable_local_evaluation = enable_local_evaluation + self.environment_refresh_interval_seconds = environment_refresh_interval_seconds self.offline_handler = offline_handler self.default_flag_handler = default_flag_handler + self.enable_realtime_updates = enable_realtime_updates self._analytics_processor = None self._environment = None @@ -93,6 +103,11 @@ def __init__( "Cannot use both default_flag_handler and offline_handler." ) + if enable_realtime_updates and not enable_local_evaluation: + raise ValueError( + "Can only use realtime updates when running in local evaluation mode." + ) + if self.offline_handler: self._environment = self.offline_handler.get_environment() @@ -110,6 +125,13 @@ def __init__( api_url = api_url or DEFAULT_API_URL self.api_url = api_url if api_url.endswith("/") else f"{api_url}/" + realtime_api_url = realtime_api_url or DEFAULT_REALTIME_API_URL + self.realtime_api_url = ( + realtime_api_url + if realtime_api_url.endswith("/") + else f"{realtime_api_url}/" + ) + self.request_timeout_seconds = request_timeout_seconds self.session.mount(self.api_url, HTTPAdapter(max_retries=retries)) @@ -124,20 +146,60 @@ def __init__( "in the environment settings page." ) - self.environment_data_polling_manager_thread = ( - EnvironmentDataPollingManager( - main=self, - refresh_interval_seconds=environment_refresh_interval_seconds, - daemon=True, # noqa - ) - ) - self.environment_data_polling_manager_thread.start() + self._initialise_local_evaluation() if enable_analytics: self._analytics_processor = AnalyticsProcessor( environment_key, self.api_url, timeout=self.request_timeout_seconds ) + def _initialise_local_evaluation(self) -> None: + if self.enable_realtime_updates: + self.update_environment() + stream_url = f"{self.realtime_api_url}sse/environments/{self._environment.api_key}/stream" + + self.event_stream_thread = EventStreamManager( + stream_url=stream_url, + on_event=self.handle_stream_event, + daemon=True, + ) + + self.event_stream_thread.start() + + else: + self.environment_data_polling_manager_thread = ( + EnvironmentDataPollingManager( + main=self, + refresh_interval_seconds=self.environment_refresh_interval_seconds, + daemon=True, + ) + ) + + self.environment_data_polling_manager_thread.start() + + def handle_stream_event(self, event: StreamEvent) -> None: + try: + event_data = json.loads(event.data) + except json.JSONDecodeError as e: + raise FlagsmithAPIError("Unable to get valid json from event data.") from e + + try: + stream_updated_at = datetime.fromtimestamp(event_data.get("updated_at")) + except TypeError as e: + raise FlagsmithAPIError( + "Unable to get valid timestamp from event data." + ) from e + + if stream_updated_at.tzinfo is None: + stream_updated_at = pytz.utc.localize(stream_updated_at) + + environment_updated_at = self._environment.updated_at + if environment_updated_at.tzinfo is None: + environment_updated_at = pytz.utc.localize(environment_updated_at) + + if stream_updated_at > environment_updated_at: + self.update_environment() + def get_environment_flags(self) -> Flags: """ Get all the default for flags for the current environment. @@ -267,7 +329,7 @@ def _get_json_response(self, url: str, method: str, body: dict = None): response.status_code, ) return response.json() - except (requests.ConnectionError, JSONDecodeError) as e: + except (requests.ConnectionError, json.JSONDecodeError) as e: raise FlagsmithAPIError( "Unable to get valid response from Flagsmith API." ) from e @@ -291,3 +353,6 @@ def _build_identity_model(self, identifier: str, **traits): def __del__(self): if hasattr(self, "environment_data_polling_manager_thread"): self.environment_data_polling_manager_thread.stop() + + if hasattr(self, "event_stream_thread"): + self.event_stream_thread.stop() diff --git a/flagsmith/streaming_manager.py b/flagsmith/streaming_manager.py new file mode 100644 index 0000000..64ac6a7 --- /dev/null +++ b/flagsmith/streaming_manager.py @@ -0,0 +1,57 @@ +import logging +import threading +from typing import Callable, Generator, Optional, Protocol, cast + +import requests +import sseclient + +from flagsmith.exceptions import FlagsmithAPIError + +logger = logging.getLogger(__name__) + + +class StreamEvent(Protocol): + data: str + + +class EventStreamManager(threading.Thread): + def __init__( + self, + *args, + stream_url: str, + on_event: Callable[[StreamEvent], None], + request_timeout_seconds: Optional[int] = None, + **kwargs + ) -> None: + super().__init__(*args, **kwargs) + self._stop_event = threading.Event() + self.stream_url = stream_url + self.on_event = on_event + self.request_timeout_seconds = request_timeout_seconds + + def run(self) -> None: + while not self._stop_event.is_set(): + try: + with requests.get( + self.stream_url, + stream=True, + headers={"Accept": "application/json, text/event-stream"}, + timeout=self.request_timeout_seconds, + ) as response: + sse_client = sseclient.SSEClient( + cast(Generator[bytes, None, None], response) + ) + for event in sse_client.events(): + self.on_event(event) + + except requests.exceptions.ReadTimeout: + pass + + except (FlagsmithAPIError, requests.RequestException): + logger.exception("Error handling event stream") + + def stop(self) -> None: + self._stop_event.set() + + def __del__(self) -> None: + self._stop_event.set() diff --git a/poetry.lock b/poetry.lock index e9e4411..63bb41a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -213,13 +213,13 @@ files = [ [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] @@ -253,12 +253,12 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p [[package]] name = "flagsmith-flag-engine" -version = "5.0.0" +version = "5.1.1" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" files = [ - {file = "flagsmith-flag-engine-5.0.0.tar.gz", hash = "sha256:d89a894f8c79da3b5c4688b979e16d038b29bc34c1dbfea48f74be26d60e4ac9"}, + {file = "flagsmith-flag-engine-5.1.1.tar.gz", hash = "sha256:a97d001ac50fcddee273a25d8c88442b33797fde5b4d657f3e83e1493aa4f536"}, ] [package.dependencies] @@ -299,13 +299,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -481,19 +481,19 @@ files = [ [[package]] name = "pydantic" -version = "2.5.2" +version = "2.5.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-2.5.2-py3-none-any.whl", hash = "sha256:80c50fb8e3dcecfddae1adbcc00ec5822918490c99ab31f6cf6140ca1c1429f0"}, - {file = "pydantic-2.5.2.tar.gz", hash = "sha256:ff177ba64c6faf73d7afa2e8cad38fd456c0dbe01c9954e71038001cd15a6edd"}, + {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, + {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, ] [package.dependencies] annotated-types = ">=0.4.0" importlib-metadata = {version = "*", markers = "python_version == \"3.7\""} -pydantic-core = "2.14.5" +pydantic-core = "2.14.6" typing-extensions = ">=4.6.1" [package.extras] @@ -501,13 +501,13 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-collections" -version = "0.5.1" +version = "0.5.2" description = "Collections of pydantic models" optional = false python-versions = "*" files = [ - {file = "pydantic-collections-0.5.1.tar.gz", hash = "sha256:8b4d2f5a7052dab8d8036cc3d5b013dba20809fd4f43599002a90f40da4653bd"}, - {file = "pydantic_collections-0.5.1-py3-none-any.whl", hash = "sha256:b58f5b17946d997d14f2d92a4d6a0a31b10940dc9e40fcda1ae31bd2d7e62e22"}, + {file = "pydantic-collections-0.5.2.tar.gz", hash = "sha256:48d1317e55342e3df6403a900e8a326d3c8452300c3a9c29e1cf032e09409454"}, + {file = "pydantic_collections-0.5.2-py3-none-any.whl", hash = "sha256:5540c645759e5d52b56b181639c22748936c04d3850cfaa2ac04f2c7169698ba"}, ] [package.dependencies] @@ -516,116 +516,116 @@ typing-extensions = ">=4.7.1" [[package]] name = "pydantic-core" -version = "2.14.5" +version = "2.14.6" description = "" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic_core-2.14.5-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:7e88f5696153dc516ba6e79f82cc4747e87027205f0e02390c21f7cb3bd8abfd"}, - {file = "pydantic_core-2.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4641e8ad4efb697f38a9b64ca0523b557c7931c5f84e0fd377a9a3b05121f0de"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774de879d212db5ce02dfbf5b0da9a0ea386aeba12b0b95674a4ce0593df3d07"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebb4e035e28f49b6f1a7032920bb9a0c064aedbbabe52c543343d39341a5b2a3"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b53e9ad053cd064f7e473a5f29b37fc4cc9dc6d35f341e6afc0155ea257fc911"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aa1768c151cf562a9992462239dfc356b3d1037cc5a3ac829bb7f3bda7cc1f9"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eac5c82fc632c599f4639a5886f96867ffced74458c7db61bc9a66ccb8ee3113"}, - {file = "pydantic_core-2.14.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae91f50ccc5810b2f1b6b858257c9ad2e08da70bf890dee02de1775a387c66"}, - {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6b9ff467ffbab9110e80e8c8de3bcfce8e8b0fd5661ac44a09ae5901668ba997"}, - {file = "pydantic_core-2.14.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61ea96a78378e3bd5a0be99b0e5ed00057b71f66115f5404d0dae4819f495093"}, - {file = "pydantic_core-2.14.5-cp310-none-win32.whl", hash = "sha256:bb4c2eda937a5e74c38a41b33d8c77220380a388d689bcdb9b187cf6224c9720"}, - {file = "pydantic_core-2.14.5-cp310-none-win_amd64.whl", hash = "sha256:b7851992faf25eac90bfcb7bfd19e1f5ffa00afd57daec8a0042e63c74a4551b"}, - {file = "pydantic_core-2.14.5-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:4e40f2bd0d57dac3feb3a3aed50f17d83436c9e6b09b16af271b6230a2915459"}, - {file = "pydantic_core-2.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab1cdb0f14dc161ebc268c09db04d2c9e6f70027f3b42446fa11c153521c0e88"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aae7ea3a1c5bb40c93cad361b3e869b180ac174656120c42b9fadebf685d121b"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60b7607753ba62cf0739177913b858140f11b8af72f22860c28eabb2f0a61937"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2248485b0322c75aee7565d95ad0e16f1c67403a470d02f94da7344184be770f"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:823fcc638f67035137a5cd3f1584a4542d35a951c3cc68c6ead1df7dac825c26"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96581cfefa9123accc465a5fd0cc833ac4d75d55cc30b633b402e00e7ced00a6"}, - {file = "pydantic_core-2.14.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a33324437018bf6ba1bb0f921788788641439e0ed654b233285b9c69704c27b4"}, - {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9bd18fee0923ca10f9a3ff67d4851c9d3e22b7bc63d1eddc12f439f436f2aada"}, - {file = "pydantic_core-2.14.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:853a2295c00f1d4429db4c0fb9475958543ee80cfd310814b5c0ef502de24dda"}, - {file = "pydantic_core-2.14.5-cp311-none-win32.whl", hash = "sha256:cb774298da62aea5c80a89bd58c40205ab4c2abf4834453b5de207d59d2e1651"}, - {file = "pydantic_core-2.14.5-cp311-none-win_amd64.whl", hash = "sha256:e87fc540c6cac7f29ede02e0f989d4233f88ad439c5cdee56f693cc9c1c78077"}, - {file = "pydantic_core-2.14.5-cp311-none-win_arm64.whl", hash = "sha256:57d52fa717ff445cb0a5ab5237db502e6be50809b43a596fb569630c665abddf"}, - {file = "pydantic_core-2.14.5-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:e60f112ac88db9261ad3a52032ea46388378034f3279c643499edb982536a093"}, - {file = "pydantic_core-2.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e227c40c02fd873c2a73a98c1280c10315cbebe26734c196ef4514776120aeb"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0cbc7fff06a90bbd875cc201f94ef0ee3929dfbd5c55a06674b60857b8b85ed"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:103ef8d5b58596a731b690112819501ba1db7a36f4ee99f7892c40da02c3e189"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c949f04ecad823f81b1ba94e7d189d9dfb81edbb94ed3f8acfce41e682e48cef"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1452a1acdf914d194159439eb21e56b89aa903f2e1c65c60b9d874f9b950e5d"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4679d4c2b089e5ef89756bc73e1926745e995d76e11925e3e96a76d5fa51fc"}, - {file = "pydantic_core-2.14.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf9d3fe53b1ee360e2421be95e62ca9b3296bf3f2fb2d3b83ca49ad3f925835e"}, - {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70f4b4851dbb500129681d04cc955be2a90b2248d69273a787dda120d5cf1f69"}, - {file = "pydantic_core-2.14.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:59986de5710ad9613ff61dd9b02bdd2f615f1a7052304b79cc8fa2eb4e336d2d"}, - {file = "pydantic_core-2.14.5-cp312-none-win32.whl", hash = "sha256:699156034181e2ce106c89ddb4b6504c30db8caa86e0c30de47b3e0654543260"}, - {file = "pydantic_core-2.14.5-cp312-none-win_amd64.whl", hash = "sha256:5baab5455c7a538ac7e8bf1feec4278a66436197592a9bed538160a2e7d11e36"}, - {file = "pydantic_core-2.14.5-cp312-none-win_arm64.whl", hash = "sha256:e47e9a08bcc04d20975b6434cc50bf82665fbc751bcce739d04a3120428f3e27"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:af36f36538418f3806048f3b242a1777e2540ff9efaa667c27da63d2749dbce0"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:45e95333b8418ded64745f14574aa9bfc212cb4fbeed7a687b0c6e53b5e188cd"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e47a76848f92529879ecfc417ff88a2806438f57be4a6a8bf2961e8f9ca9ec7"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d81e6987b27bc7d101c8597e1cd2bcaa2fee5e8e0f356735c7ed34368c471550"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34708cc82c330e303f4ce87758828ef6e457681b58ce0e921b6e97937dd1e2a3"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c1988019752138b974c28f43751528116bcceadad85f33a258869e641d753"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e4d090e73e0725b2904fdbdd8d73b8802ddd691ef9254577b708d413bf3006e"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5c7d5b5005f177764e96bd584d7bf28d6e26e96f2a541fdddb934c486e36fd59"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a71891847f0a73b1b9eb86d089baee301477abef45f7eaf303495cd1473613e4"}, - {file = "pydantic_core-2.14.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a717aef6971208f0851a2420b075338e33083111d92041157bbe0e2713b37325"}, - {file = "pydantic_core-2.14.5-cp37-none-win32.whl", hash = "sha256:de790a3b5aa2124b8b78ae5faa033937a72da8efe74b9231698b5a1dd9be3405"}, - {file = "pydantic_core-2.14.5-cp37-none-win_amd64.whl", hash = "sha256:6c327e9cd849b564b234da821236e6bcbe4f359a42ee05050dc79d8ed2a91588"}, - {file = "pydantic_core-2.14.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ef98ca7d5995a82f43ec0ab39c4caf6a9b994cb0b53648ff61716370eadc43cf"}, - {file = "pydantic_core-2.14.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6eae413494a1c3f89055da7a5515f32e05ebc1a234c27674a6956755fb2236f"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcf4e6d85614f7a4956c2de5a56531f44efb973d2fe4a444d7251df5d5c4dcfd"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6637560562134b0e17de333d18e69e312e0458ee4455bdad12c37100b7cad706"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77fa384d8e118b3077cccfcaf91bf83c31fe4dc850b5e6ee3dc14dc3d61bdba1"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16e29bad40bcf97aac682a58861249ca9dcc57c3f6be22f506501833ddb8939c"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531f4b4252fac6ca476fbe0e6f60f16f5b65d3e6b583bc4d87645e4e5ddde331"}, - {file = "pydantic_core-2.14.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:074f3d86f081ce61414d2dc44901f4f83617329c6f3ab49d2bc6c96948b2c26b"}, - {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c2adbe22ab4babbca99c75c5d07aaf74f43c3195384ec07ccbd2f9e3bddaecec"}, - {file = "pydantic_core-2.14.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0f6116a558fd06d1b7c2902d1c4cf64a5bd49d67c3540e61eccca93f41418124"}, - {file = "pydantic_core-2.14.5-cp38-none-win32.whl", hash = "sha256:fe0a5a1025eb797752136ac8b4fa21aa891e3d74fd340f864ff982d649691867"}, - {file = "pydantic_core-2.14.5-cp38-none-win_amd64.whl", hash = "sha256:079206491c435b60778cf2b0ee5fd645e61ffd6e70c47806c9ed51fc75af078d"}, - {file = "pydantic_core-2.14.5-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:a6a16f4a527aae4f49c875da3cdc9508ac7eef26e7977952608610104244e1b7"}, - {file = "pydantic_core-2.14.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:abf058be9517dc877227ec3223f0300034bd0e9f53aebd63cf4456c8cb1e0863"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49b08aae5013640a3bfa25a8eebbd95638ec3f4b2eaf6ed82cf0c7047133f03b"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2d97e906b4ff36eb464d52a3bc7d720bd6261f64bc4bcdbcd2c557c02081ed2"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3128e0bbc8c091ec4375a1828d6118bc20404883169ac95ffa8d983b293611e6"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88e74ab0cdd84ad0614e2750f903bb0d610cc8af2cc17f72c28163acfcf372a4"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c339dabd8ee15f8259ee0f202679b6324926e5bc9e9a40bf981ce77c038553db"}, - {file = "pydantic_core-2.14.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3387277f1bf659caf1724e1afe8ee7dbc9952a82d90f858ebb931880216ea955"}, - {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ba6b6b3846cfc10fdb4c971980a954e49d447cd215ed5a77ec8190bc93dd7bc5"}, - {file = "pydantic_core-2.14.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca61d858e4107ce5e1330a74724fe757fc7135190eb5ce5c9d0191729f033209"}, - {file = "pydantic_core-2.14.5-cp39-none-win32.whl", hash = "sha256:ec1e72d6412f7126eb7b2e3bfca42b15e6e389e1bc88ea0069d0cc1742f477c6"}, - {file = "pydantic_core-2.14.5-cp39-none-win_amd64.whl", hash = "sha256:c0b97ec434041827935044bbbe52b03d6018c2897349670ff8fe11ed24d1d4ab"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:79e0a2cdbdc7af3f4aee3210b1172ab53d7ddb6a2d8c24119b5706e622b346d0"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:678265f7b14e138d9a541ddabbe033012a2953315739f8cfa6d754cc8063e8ca"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b15e855ae44f0c6341ceb74df61b606e11f1087e87dcb7482377374aac6abe"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09b0e985fbaf13e6b06a56d21694d12ebca6ce5414b9211edf6f17738d82b0f8"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ad873900297bb36e4b6b3f7029d88ff9829ecdc15d5cf20161775ce12306f8a"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2d0ae0d8670164e10accbeb31d5ad45adb71292032d0fdb9079912907f0085f4"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d37f8ec982ead9ba0a22a996129594938138a1503237b87318392a48882d50b7"}, - {file = "pydantic_core-2.14.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:35613015f0ba7e14c29ac6c2483a657ec740e5ac5758d993fdd5870b07a61d8b"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab4ea451082e684198636565224bbb179575efc1658c48281b2c866bfd4ddf04"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ce601907e99ea5b4adb807ded3570ea62186b17f88e271569144e8cca4409c7"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb2ed8b3fe4bf4506d6dab3b93b83bbc22237e230cba03866d561c3577517d18"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70f947628e074bb2526ba1b151cee10e4c3b9670af4dbb4d73bc8a89445916b5"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4bc536201426451f06f044dfbf341c09f540b4ebdb9fd8d2c6164d733de5e634"}, - {file = "pydantic_core-2.14.5-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4791cf0f8c3104ac668797d8c514afb3431bc3305f5638add0ba1a5a37e0d88"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:038c9f763e650712b899f983076ce783175397c848da04985658e7628cbe873b"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:27548e16c79702f1e03f5628589c6057c9ae17c95b4c449de3c66b589ead0520"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97bee68898f3f4344eb02fec316db93d9700fb1e6a5b760ffa20d71d9a46ce3"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b759b77f5337b4ea024f03abc6464c9f35d9718de01cfe6bae9f2e139c397e"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:439c9afe34638ace43a49bf72d201e0ffc1a800295bed8420c2a9ca8d5e3dbb3"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ba39688799094c75ea8a16a6b544eb57b5b0f3328697084f3f2790892510d144"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ccd4d5702bb90b84df13bd491be8d900b92016c5a455b7e14630ad7449eb03f8"}, - {file = "pydantic_core-2.14.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:81982d78a45d1e5396819bbb4ece1fadfe5f079335dd28c4ab3427cd95389944"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7f8210297b04e53bc3da35db08b7302a6a1f4889c79173af69b72ec9754796b8"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8c8a8812fe6f43a3a5b054af6ac2d7b8605c7bcab2804a8a7d68b53f3cd86e00"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:206ed23aecd67c71daf5c02c3cd19c0501b01ef3cbf7782db9e4e051426b3d0d"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2027d05c8aebe61d898d4cffd774840a9cb82ed356ba47a90d99ad768f39789"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40180930807ce806aa71eda5a5a5447abb6b6a3c0b4b3b1b1962651906484d68"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:615a0a4bff11c45eb3c1996ceed5bdaa2f7b432425253a7c2eed33bb86d80abc"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5e412d717366e0677ef767eac93566582518fe8be923361a5c204c1a62eaafe"}, - {file = "pydantic_core-2.14.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:513b07e99c0a267b1d954243845d8a833758a6726a3b5d8948306e3fe14675e3"}, - {file = "pydantic_core-2.14.5.tar.gz", hash = "sha256:6d30226dfc816dd0fdf120cae611dd2215117e4f9b124af8c60ab9093b6e8e71"}, + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, + {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, + {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, + {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, + {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, + {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, + {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, + {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, + {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, + {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, + {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, + {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, + {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, + {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, + {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, + {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, ] [package.dependencies] @@ -644,13 +644,13 @@ files = [ [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -682,6 +682,17 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytz" +version = "2023.4" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, + {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, +] + [[package]] name = "pyyaml" version = "6.0.1" @@ -781,22 +792,24 @@ dev = ["black (>=22.3.0)", "build (>=0.7.0)", "isort (>=5.11.4)", "pyflakes (>=2 [[package]] name = "responses" -version = "0.17.0" +version = "0.23.3" description = "A utility library for mocking out the `requests` Python library." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "responses-0.17.0-py2.py3-none-any.whl", hash = "sha256:e4fc472fb7374fb8f84fcefa51c515ca4351f198852b4eb7fc88223780b472ea"}, - {file = "responses-0.17.0.tar.gz", hash = "sha256:ec675e080d06bf8d1fb5e5a68a1e5cd0df46b09c78230315f650af5e4036bec7"}, + {file = "responses-0.23.3-py3-none-any.whl", hash = "sha256:e6fbcf5d82172fecc0aa1860fd91e58cbfd96cee5e96da5b63fa6eb3caa10dd3"}, + {file = "responses-0.23.3.tar.gz", hash = "sha256:205029e1cb334c21cb4ec64fc7599be48b859a0fd381a42443cdd600bfe8b16a"}, ] [package.dependencies] -requests = ">=2.0" -six = "*" -urllib3 = ">=1.25.10" +pyyaml = "*" +requests = ">=2.30.0,<3.0" +types-PyYAML = "*" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=3.7.1,<6.0.0)", "flake8", "mypy", "pytest (>=4.6)", "pytest (>=4.6,<5.0)", "pytest-cov", "pytest-localserver", "types-mock", "types-requests", "types-six"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-requests"] [[package]] name = "semver" @@ -826,14 +839,14 @@ testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[l testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" +name = "sseclient-py" +version = "1.8.0" +description = "SSE client for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "*" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "sseclient-py-1.8.0.tar.gz", hash = "sha256:c547c5c1a7633230a38dc599a21a2dc638f9b5c297286b48b46b935c71fac3e8"}, + {file = "sseclient_py-1.8.0-py2.py3-none-any.whl", hash = "sha256:4ecca6dc0b9f963f8384e9d7fd529bf93dd7d708144c4fb5da0e0a1a926fee83"}, ] [[package]] @@ -897,6 +910,17 @@ files = [ {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.12" +description = "Typing stubs for PyYAML" +optional = false +python-versions = "*" +files = [ + {file = "types-PyYAML-6.0.12.12.tar.gz", hash = "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062"}, + {file = "types_PyYAML-6.0.12.12-py3-none-any.whl", hash = "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"}, +] + [[package]] name = "typing-extensions" version = "4.7.1" @@ -964,4 +988,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = ">=3.7.0,<4" -content-hash = "d513b76114ceb0b46104589ab61cda88138ec8256b904d7aadfd04bc606497b4" +content-hash = "a137ed145a3f91426ee3973bc0166357799ebfaca0807a5941b4d58fa6253b70" diff --git a/pyproject.toml b/pyproject.toml index 59003b9..59fde13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,13 +14,15 @@ python = ">=3.7.0,<4" requests = "^2.27.1" requests-futures = "^1.0.0" flagsmith-flag-engine = "^5.0.0" +sseclient-py = "^1.8.0" +pytz = "^2023.4" [tool.poetry.dev-dependencies] pytest = "^7.4.0" pytest-mock = "^3.6.1" black = "^23.3.0" pre-commit = "^2.17.0" -responses = "^0.17.0" +responses = "^0.23.3" flake8 = "^4.0.1" isort = "^5.10.1" diff --git a/tests/conftest.py b/tests/conftest.py index b8105a0..d13c761 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,9 +2,12 @@ import os import random import string +from typing import Generator import pytest +import responses from flag_engine.environments.models import EnvironmentModel +from pytest_mock import MockerFixture from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor @@ -41,7 +44,9 @@ def environment_json(): @pytest.fixture() -def local_eval_flagsmith(server_api_key, environment_json, mocker): +def requests_session_response_ok( + mocker: Generator[MockerFixture, None, None], environment_json: str +) -> None: mock_session = mocker.MagicMock() mocker.patch("flagsmith.flagsmith.requests.Session", return_value=mock_session) @@ -49,6 +54,11 @@ def local_eval_flagsmith(server_api_key, environment_json, mocker): mock_environment_document_response.json.return_value = json.loads(environment_json) mock_session.get.return_value = mock_environment_document_response + +@pytest.fixture() +def local_eval_flagsmith( + requests_session_response_ok: None, server_api_key: str +) -> Generator[Flagsmith, None, None]: flagsmith = Flagsmith( environment_key=server_api_key, enable_local_evaluation=True, @@ -75,3 +85,9 @@ def flags_json(): def identities_json(): with open(os.path.join(DATA_DIR, "identities.json"), "rt") as f: yield f.read() + + +@pytest.fixture +def mocked_responses() -> Generator["responses.RequestsMock", None, None]: + with responses.RequestsMock() as rsps: + yield rsps diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index baaa9cb..5c1cd3b 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -421,8 +421,8 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( offline_handler=mock_offline_handler, ) - responses.add(flagsmith.environment_flags_url, status=500) - responses.add(flagsmith.identities_url, status=500) + responses.get(flagsmith.environment_flags_url, status=500) + responses.get(flagsmith.identities_url, status=500) # When environment_flags = flagsmith.get_environment_flags() @@ -474,3 +474,41 @@ def test_cannot_create_flagsmith_client_in_remote_evaluation_without_api_key(): # Then assert e.exconly() == "ValueError: environment_key is required." + + +def test_stream_not_used_by_default( + requests_session_response_ok: None, server_api_key: str +) -> None: + # When + flagsmith = Flagsmith( + environment_key=server_api_key, + enable_local_evaluation=True, + ) + + # Then + assert hasattr(flagsmith, "event_stream_thread") is False + + +def test_stream_used_when_enable_realtime_updates_is_true( + requests_session_response_ok: None, server_api_key: str +) -> None: + # When + flagsmith = Flagsmith( + environment_key=server_api_key, + enable_local_evaluation=True, + enable_realtime_updates=True, + ) + + # Then + assert hasattr(flagsmith, "event_stream_thread") is True + + +def test_error_raised_when_realtime_updates_is_true_and_local_evaluation_false( + requests_session_response_ok: None, server_api_key: str +) -> None: + with pytest.raises(ValueError): + Flagsmith( + environment_key=server_api_key, + enable_local_evaluation=False, + enable_realtime_updates=True, + ) diff --git a/tests/test_streaming_manager.py b/tests/test_streaming_manager.py new file mode 100644 index 0000000..136f40b --- /dev/null +++ b/tests/test_streaming_manager.py @@ -0,0 +1,177 @@ +import time +from datetime import datetime +from typing import Generator +from unittest.mock import MagicMock, Mock + +import pytest +import requests +import responses +from pytest_mock import MockerFixture + +from flagsmith import Flagsmith +from flagsmith.exceptions import FlagsmithAPIError +from flagsmith.streaming_manager import EventStreamManager + + +def test_stream_manager_handles_timeout( + mocked_responses: Generator["responses.RequestsMock", None, None] +) -> None: + stream_url = ( + "https://realtime.flagsmith.com/sse/environments/B62qaMZNwfiqT76p38ggrQ/stream" + ) + + mocked_responses.get(stream_url, body=requests.exceptions.ReadTimeout()) + + streaming_manager = EventStreamManager( + stream_url=stream_url, + on_event=MagicMock(), + daemon=True, + ) + + streaming_manager.start() + + time.sleep(0.01) + + assert streaming_manager.is_alive() + + streaming_manager.stop() + + +def test_stream_manager_handles_request_exception( + mocked_responses: Generator["responses.RequestsMock", None, None], + caplog: Generator["pytest.LogCaptureFixture", None, None], +) -> None: + stream_url = ( + "https://realtime.flagsmith.com/sse/environments/B62qaMZNwfiqT76p38ggrQ/stream" + ) + + mocked_responses.get(stream_url, body=requests.RequestException()) + mocked_responses.get(stream_url, body=FlagsmithAPIError()) + + streaming_manager = EventStreamManager( + stream_url=stream_url, + on_event=MagicMock(), + daemon=True, + ) + + streaming_manager.start() + + time.sleep(0.01) + + assert streaming_manager.is_alive() + + streaming_manager.stop() + + for record in caplog.records: + assert record.levelname == "ERROR" + assert record.message == "Error handling event stream" + + +def test_environment_updates_on_recent_event( + server_api_key: str, mocker: MockerFixture +) -> None: + stream_updated_at = datetime(2020, 1, 1, 1, 1, 2) + environment_updated_at = datetime(2020, 1, 1, 1, 1, 1) + + mocker.patch("flagsmith.Flagsmith.update_environment") + + flagsmith = Flagsmith(environment_key=server_api_key) + flagsmith._environment = MagicMock() + flagsmith._environment.updated_at = environment_updated_at + + flagsmith.handle_stream_event( + event=Mock( + data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', + ) + ) + + flagsmith.update_environment.assert_called_once() + + +def test_environment_does_not_update_on_past_event( + server_api_key: str, mocker: MockerFixture +) -> None: + stream_updated_at = datetime(2020, 1, 1, 1, 1, 1) + environment_updated_at = datetime(2020, 1, 1, 1, 1, 2) + + mocker.patch("flagsmith.Flagsmith.update_environment") + + flagsmith = Flagsmith(environment_key=server_api_key) + flagsmith._environment = MagicMock() + flagsmith._environment.updated_at = environment_updated_at + + flagsmith.handle_stream_event( + event=Mock( + data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', + ) + ) + + flagsmith.update_environment.assert_not_called() + + +def test_environment_does_not_update_on_same_event( + server_api_key: str, mocker: MockerFixture +) -> None: + stream_updated_at = datetime(2020, 1, 1, 1, 1, 1) + environment_updated_at = datetime(2020, 1, 1, 1, 1, 1) + + mocker.patch("flagsmith.Flagsmith.update_environment") + + flagsmith = Flagsmith(environment_key=server_api_key) + flagsmith._environment = MagicMock() + flagsmith._environment.updated_at = environment_updated_at + + flagsmith.handle_stream_event( + event=Mock( + data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', + ) + ) + + flagsmith.update_environment.assert_not_called() + + +def test_invalid_json_payload(server_api_key: str, mocker: MockerFixture) -> None: + mocker.patch("flagsmith.Flagsmith.update_environment") + flagsmith = Flagsmith(environment_key=server_api_key) + + with pytest.raises(FlagsmithAPIError): + flagsmith.handle_stream_event( + event=Mock( + data='{"updated_at": test}\n\n', + ) + ) + + with pytest.raises(FlagsmithAPIError): + flagsmith.handle_stream_event( + event=Mock( + data="{{test}}\n\n", + ) + ) + + with pytest.raises(FlagsmithAPIError): + flagsmith.handle_stream_event( + event=Mock( + data="test", + ) + ) + + +def test_invalid_timestamp_in_payload( + server_api_key: str, mocker: MockerFixture +) -> None: + mocker.patch("flagsmith.Flagsmith.update_environment") + flagsmith = Flagsmith(environment_key=server_api_key) + + with pytest.raises(FlagsmithAPIError): + flagsmith.handle_stream_event( + event=Mock( + data='{"updated_at": "test"}\n\n', + ) + ) + + with pytest.raises(FlagsmithAPIError): + flagsmith.handle_stream_event( + event=Mock( + data='{"test": "test"}\n\n', + ) + ) From cc47ae372154bb1840bc849b77b13b3902de2b27 Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Wed, 28 Feb 2024 15:15:23 +0000 Subject: [PATCH 034/121] chore: remove examples (#75) * chore/remove examples * Update .isort.cfg --- example/.env.template | 2 -- example/__init__.py | 0 example/app.py | 63 ------------------------------------- example/readme.md | 22 ------------- example/requirements.txt | 2 -- example/templates/home.html | 24 -------------- 6 files changed, 113 deletions(-) delete mode 100644 example/.env.template delete mode 100644 example/__init__.py delete mode 100644 example/app.py delete mode 100644 example/readme.md delete mode 100644 example/requirements.txt delete mode 100644 example/templates/home.html diff --git a/example/.env.template b/example/.env.template deleted file mode 100644 index 7af4488..0000000 --- a/example/.env.template +++ /dev/null @@ -1,2 +0,0 @@ -FLASK_APP=app -FLAGSMITH_ENVIRONMENT_KEY= \ No newline at end of file diff --git a/example/__init__.py b/example/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/example/app.py b/example/app.py deleted file mode 100644 index 034677b..0000000 --- a/example/app.py +++ /dev/null @@ -1,63 +0,0 @@ -import json -import os - -from flask import Flask, render_template, request - -from flagsmith import Flagsmith -from flagsmith.models import DefaultFlag - -app = Flask(__name__) - - -def default_flag_handler(feature_name: str) -> DefaultFlag: - """ - Function that will be used if the API doesn't respond, or an unknown - feature is requested - """ - - if feature_name == "secret_button": - return DefaultFlag( - enabled=False, - value=json.dumps({"colour": "#b8b8b8"}), - ) - - return DefaultFlag(False, None) - - -flagsmith = Flagsmith( - environment_key=os.environ.get("FLAGSMITH_ENVIRONMENT_KEY"), - default_flag_handler=default_flag_handler, -) - - -@app.route("/", methods=["GET", "POST"]) -def home(): - if request.args: - identifier = request.args["identifier"] - - trait_key = request.args.get("trait-key") - trait_value = request.args.get("trait-value") - traits = {trait_key: trait_value} if trait_key else None - - # Get the flags for an identity, including the provided trait which will be - # persisted to the API for future requests. - identity_flags = flagsmith.get_identity_flags( - identifier=identifier, traits=traits - ) - show_button = identity_flags.is_feature_enabled("secret_button") - button_data = json.loads(identity_flags.get_feature_value("secret_button")) - return render_template( - "home.html", - show_button=show_button, - button_colour=button_data["colour"], - identifier=identifier, - ) - - # Get the default flags for the current environment - flags = flagsmith.get_environment_flags() - show_button = flags.is_feature_enabled("secret_button") - button_data = json.loads(flags.get_feature_value("secret_button")) - - return render_template( - "home.html", show_button=show_button, button_colour=button_data["colour"] - ) diff --git a/example/readme.md b/example/readme.md deleted file mode 100644 index 7b61346..0000000 --- a/example/readme.md +++ /dev/null @@ -1,22 +0,0 @@ -# Flagsmith Basic Python Example - -This directory contains a basic Flask application which utilises Flagsmith. To run the example application, you'll need -to go through the following steps: - -1. Create an account, organisation and project on [Flagsmith](https://flagsmith.com) -2. Create a feature in the project called "secret_button" -3. Give the feature a value using the json editor as follows: - -```json -{ "colour": "#ababab" } -``` - -4. Create a .env file from the template located in this directory with the environment key of one of the environments in - flagsmith (This can be found on the 'settings' page accessed from the menu on the left under the chosen environment.) -5. From a terminal window, export those environment variables (either manually or by using `export $(cat .env)`) -6. Run the app using `flask run` -7. Browse to http://localhost:5000 - -Now you can play around with the 'secret_button' feature in flagsmith, turn it on to show it and edit the colour in the -json value to edit the colour of the button. You can also identify as a given user and then update the settings for the -secret button feature for that user in the flagsmith interface to see the affect that has too. diff --git a/example/requirements.txt b/example/requirements.txt deleted file mode 100644 index 6be4940..0000000 --- a/example/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask==2.2.5 -flagsmith>=3.0.0 \ No newline at end of file diff --git a/example/templates/home.html b/example/templates/home.html deleted file mode 100644 index 23f440d..0000000 --- a/example/templates/home.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - -Flagsmith Example - -

Hello, {{ identifier or 'World' }}.

- {% if show_button %} - - {% endif %} - -

- -
-

Identify as a user

-
- -

... with an optional user trait

-
-

- - -
- From f8fd38737e168d6c9c7710496ed3032ce7f70550 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 7 Mar 2024 16:31:44 +0000 Subject: [PATCH 035/121] feat: Add indentity overrides to local evaluation mode (#72) - Bump flag-engine - Add environment overrides parsing - Add pytest-cov - Fix black pre-commit hook - Fix Flag dataclasses --- .gitignore | 2 + .pre-commit-config.yaml | 2 +- flagsmith/flagsmith.py | 29 +- flagsmith/models.py | 15 +- poetry.lock | 590 +++++++++++++++++------------------- pyproject.toml | 5 +- tests/data/environment.json | 27 +- tests/test_flagsmith.py | 28 ++ 8 files changed, 365 insertions(+), 333 deletions(-) diff --git a/.gitignore b/.gitignore index aa2b29d..a6a5c83 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ flagsmith.egg-info/ .envrc .tool-versions + +.coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f558a5..9583036 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: stable + rev: 23.3.0 hooks: - id: black language_version: python3 diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 7a807af..a600182 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -7,7 +7,9 @@ import requests from flag_engine import engine from flag_engine.environments.models import EnvironmentModel -from flag_engine.identities.models import IdentityModel, TraitModel +from flag_engine.identities.models import IdentityModel +from flag_engine.identities.traits.models import TraitModel +from flag_engine.identities.traits.types import TraitValue from flag_engine.segments.evaluator import get_identity_segments from requests.adapters import HTTPAdapter from urllib3 import Retry @@ -94,6 +96,7 @@ def __init__( self.enable_realtime_updates = enable_realtime_updates self._analytics_processor = None self._environment = None + self._identity_overrides_by_identifier: typing.Dict[str, IdentityModel] = {} # argument validation if offline_mode and not offline_handler: @@ -248,12 +251,21 @@ def get_identity_segments( ) traits = traits or {} - identity_model = self._build_identity_model(identifier, **traits) + identity_model = self._get_identity_model(identifier, **traits) segment_models = get_identity_segments(self._environment, identity_model) return [Segment(id=sm.id, name=sm.name) for sm in segment_models] def update_environment(self): self._environment = self._get_environment_from_api() + self._update_overrides() + + def _update_overrides(self) -> None: + if not self._environment: + return + if overrides := self._environment.identity_overrides: + self._identity_overrides_by_identifier = { + identity.identifier: identity for identity in overrides + } def _get_environment_from_api(self) -> EnvironmentModel: environment_data = self._get_json_response(self.environment_url, method="GET") @@ -269,7 +281,7 @@ def _get_environment_flags_from_document(self) -> Flags: def _get_identity_flags_from_document( self, identifier: str, traits: typing.Dict[str, typing.Any] ) -> Flags: - identity_model = self._build_identity_model(identifier, **traits) + identity_model = self._get_identity_model(identifier, **traits) feature_states = engine.get_identity_feature_states( self._environment, identity_model ) @@ -334,7 +346,11 @@ def _get_json_response(self, url: str, method: str, body: dict = None): "Unable to get valid response from Flagsmith API." ) from e - def _build_identity_model(self, identifier: str, **traits): + def _get_identity_model( + self, + identifier: str, + **traits: TraitValue, + ) -> IdentityModel: if not self._environment: raise FlagsmithClientError( "Unable to build identity model when no local environment present." @@ -344,6 +360,11 @@ def _build_identity_model(self, identifier: str, **traits): TraitModel(trait_key=key, trait_value=value) for key, value in traits.items() ] + + if identity := self._identity_overrides_by_identifier.get(identifier): + identity.update_traits(trait_models) + return identity + return IdentityModel( identifier=identifier, environment_api_key=self._environment.api_key, diff --git a/flagsmith/models.py b/flagsmith/models.py index 4284774..41d0037 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -10,20 +10,19 @@ @dataclass class BaseFlag: enabled: bool - value: typing.Union[str, int, float, bool, type(None)] - is_default: bool + value: typing.Union[str, int, float, bool, None] +@dataclass class DefaultFlag(BaseFlag): - def __init__(self, *args, **kwargs): - super().__init__(*args, is_default=True, **kwargs) + is_default: bool = field(default=True) +@dataclass class Flag(BaseFlag): - def __init__(self, *args, feature_id: int, feature_name: str, **kwargs): - super().__init__(*args, is_default=False, **kwargs) - self.feature_id = feature_id - self.feature_name = feature_name + feature_id: int + feature_name: str + is_default: bool = field(default=False) @classmethod def from_feature_state_model( diff --git a/poetry.lock b/poetry.lock index 63bb41a..a733f37 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,14 +1,14 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "annotated-types" -version = "0.5.0" +version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, - {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] [package.dependencies] @@ -16,36 +16,33 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "black" -version = "23.3.0" +version = "23.12.1" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" -files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +python-versions = ">=3.8" +files = [ + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, ] [package.dependencies] @@ -55,35 +52,34 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] name = "cfgv" -version = "3.3.1" +version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8" files = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] @@ -198,7 +194,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" @@ -211,6 +206,73 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.4.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + [[package]] name = "distlib" version = "0.3.8" @@ -238,18 +300,19 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.12.2" +version = "3.13.1" description = "A platform independent file lock." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "flagsmith-flag-engine" @@ -278,20 +341,19 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "identify" -version = "2.5.24" +version = "2.5.33" description = "File identification library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, ] [package.extras] @@ -308,25 +370,6 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] -[[package]] -name = "importlib-metadata" -version = "4.2.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.6" -files = [ - {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, - {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, -] - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -340,20 +383,17 @@ files = [ [[package]] name = "isort" -version = "5.11.5" +version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, - {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "mccabe" @@ -404,47 +444,41 @@ files = [ [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" -version = "2.6.2" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} - [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] @@ -463,7 +497,6 @@ files = [ [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" @@ -481,19 +514,18 @@ files = [ [[package]] name = "pydantic" -version = "2.5.3" +version = "2.6.1" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, - {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, + {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, + {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, ] [package.dependencies] annotated-types = ">=0.4.0" -importlib-metadata = {version = "*", markers = "python_version == \"3.7\""} -pydantic-core = "2.14.6" +pydantic-core = "2.16.2" typing-extensions = ">=4.6.1" [package.extras] @@ -516,116 +548,90 @@ typing-extensions = ">=4.7.1" [[package]] name = "pydantic-core" -version = "2.14.6" +version = "2.16.2" description = "" optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, - {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, - {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, - {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, - {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, - {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, - {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, - {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, - {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, - {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, - {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, - {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, - {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, - {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, - {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, - {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, - {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, - {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, - {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, - {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, - {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, - {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, - {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, - {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, - {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, - {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, - {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, - {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, - {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, - {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, - {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, - {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, - {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, - {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, - {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, + {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, + {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, + {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, + {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, + {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, + {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, + {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, + {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, + {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, + {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, + {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, + {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, + {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, ] [package.dependencies] @@ -656,7 +662,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -665,15 +670,33 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + [[package]] name = "pytest-mock" -version = "3.11.1" +version = "3.12.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, - {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, + {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, + {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, ] [package.dependencies] @@ -805,7 +828,6 @@ files = [ pyyaml = "*" requests = ">=2.30.0,<3.0" types-PyYAML = "*" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} urllib3 = ">=1.25.10,<3.0" [package.extras] @@ -824,19 +846,19 @@ files = [ [[package]] name = "setuptools" -version = "68.0.0" +version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "sseclient-py" @@ -860,56 +882,6 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" -optional = false -python-versions = ">=3.6" -files = [ - {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, - {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, - {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, - {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, - {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, - {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, - {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, - {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, - {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, - {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, - {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, -] - [[package]] name = "types-pyyaml" version = "6.0.12.12" @@ -923,69 +895,53 @@ files = [ [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "urllib3" -version = "2.0.7" +version = "2.2.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.16.2" +version = "20.25.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, - {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -platformdirs = ">=2,<3" - -[package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] - -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [metadata] lock-version = "2.0" -python-versions = ">=3.7.0,<4" -content-hash = "a137ed145a3f91426ee3973bc0166357799ebfaca0807a5941b4d58fa6253b70" +python-versions = ">=3.8.0,<4" +content-hash = "2b957eb89c6fb847554280ba8991651b5e2d79a5f42836bfc58b70312a7c5dde" diff --git a/pyproject.toml b/pyproject.toml index 59fde13..a58a75d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,10 +10,10 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -python = ">=3.7.0,<4" +python = ">=3.8.0,<4" requests = "^2.27.1" requests-futures = "^1.0.0" -flagsmith-flag-engine = "^5.0.0" +flagsmith-flag-engine = "^5.1.0" sseclient-py = "^1.8.0" pytz = "^2023.4" @@ -28,6 +28,7 @@ isort = "^5.10.1" [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" +pytest-cov = "^4.1.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/data/environment.json b/tests/data/environment.json index afe7a44..1cd2245 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -53,5 +53,30 @@ "enabled": true } ], - "updated_at": "2023-07-14 16:12:00.000000" + "updated_at": "2023-07-14 16:12:00.000000", + "identity_overrides": [ + { + "identifier": "overridden-id", + "identity_uuid": "0f21cde8-63c5-4e50-baca-87897fa6cd01", + "created_date": "2019-08-27T14:53:45.698555Z", + "updated_at": "2023-07-14 16:12:00.000000", + "environment_api_key": "B62qaMZNwfiqT76p38ggrQ", + "identity_features": [ + { + "id": 1, + "feature": { + "id": 1, + "name": "some_feature", + "type": "STANDARD" + }, + "featurestate_uuid": "1bddb9a5-7e59-42c6-9be9-625fa369749f", + "feature_state_value": "some-overridden-value", + "enabled": false, + "environment": 1, + "identity": null, + "feature_segment": null + } + ] + } + ] } diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 5c1cd3b..9d0d23c 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -1,4 +1,5 @@ import json +import time import typing import uuid @@ -512,3 +513,30 @@ def test_error_raised_when_realtime_updates_is_true_and_local_evaluation_false( enable_local_evaluation=False, enable_realtime_updates=True, ) + + +@responses.activate() +def test_flagsmith_client_get_identity_flags__local_evaluation__returns_expected( + environment_json: str, + server_api_key: str, +) -> None: + # Given + identifier = "overridden-id" + + api_url = "https://mocked.flagsmith.com/api/v1/" + environment_document_url = f"{api_url}environment-document/" + responses.add(method="GET", url=environment_document_url, body=environment_json) + + flagsmith = Flagsmith( + environment_key=server_api_key, + api_url=api_url, + enable_local_evaluation=True, + ) + time.sleep(0.1) + + # When + flag = flagsmith.get_identity_flags(identifier).get_flag("some_feature") + + # Then + assert flag.enabled is False + assert flag.value == "some-overridden-value" From ed535ded9ba4df12e9e9eddd9093a13db31219ec Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Wed, 13 Mar 2024 18:45:49 +0000 Subject: [PATCH 036/121] feat: strict typing (#70) * feat: add mypy, update tooling * feat: add type hints to core lib (#71) --------- Co-authored-by: Tushar <30565750+tushar5526@users.noreply.github.com> Co-authored-by: Zach Aysan --- .github/workflows/pytest.yml | 9 +- .pre-commit-config.yaml | 20 +- flagsmith/__init__.py | 4 +- flagsmith/analytics.py | 19 +- flagsmith/flagsmith.py | 82 ++++-- flagsmith/models.py | 34 +-- flagsmith/polling_manager.py | 10 +- flagsmith/py.typed | 0 flagsmith/streaming_manager.py | 5 +- flagsmith/utils/identities.py | 18 +- poetry.lock | 446 ++++++++++++++++++-------------- pyproject.toml | 24 +- tests/conftest.py | 19 +- tests/test_analytics.py | 22 +- tests/test_flagsmith.py | 99 ++++--- tests/test_offline_handlers.py | 2 +- tests/test_polling_manager.py | 13 +- tests/test_streaming_manager.py | 14 +- 18 files changed, 501 insertions(+), 339 deletions(-) create mode 100644 flagsmith/py.typed diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ac2e3c4..5e01d0c 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,4 +1,4 @@ -name: Formatting and Tests +name: Linting and Tests on: - pull_request @@ -6,7 +6,7 @@ on: jobs: test: runs-on: ubuntu-latest - name: Pytest and Black formatting + name: Linting and Tests strategy: max-parallel: 4 @@ -28,7 +28,7 @@ jobs: run: | python -m pip install --upgrade pip pip install poetry - poetry install + poetry install --with dev - name: Check Formatting run: | @@ -36,5 +36,8 @@ jobs: poetry run flake8 . poetry run isort --check . + - name: Check Typing + run: poetry run mypy --strict . + - name: Run Tests run: poetry run pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9583036..857ce3a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,25 @@ repos: - - repo: https://github.com/asottile/seed-isort-config - rev: v1.9.3 + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 hooks: - - id: seed-isort-config - - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 + - id: mypy + args: [--strict] + additional_dependencies: + [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, types-pytz, sseclient-py] + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black language_version: python3 + - repo: https://github.com/pycqa/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + name: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: diff --git a/flagsmith/__init__.py b/flagsmith/__init__.py index 46571d5..4fbd5fc 100644 --- a/flagsmith/__init__.py +++ b/flagsmith/__init__.py @@ -1 +1,3 @@ -from .flagsmith import Flagsmith # noqa +from .flagsmith import Flagsmith + +__all__ = ("Flagsmith",) diff --git a/flagsmith/analytics.py b/flagsmith/analytics.py index 97b596e..dee1ed5 100644 --- a/flagsmith/analytics.py +++ b/flagsmith/analytics.py @@ -1,12 +1,13 @@ import json +import typing from datetime import datetime -from requests_futures.sessions import FuturesSession +from requests_futures.sessions import FuturesSession # type: ignore -ANALYTICS_ENDPOINT = "analytics/flags/" +ANALYTICS_ENDPOINT: typing.Final[str] = "analytics/flags/" # Used to control how often we send data(in seconds) -ANALYTICS_TIMER = 10 +ANALYTICS_TIMER: typing.Final[int] = 10 session = FuturesSession(max_workers=4) @@ -17,7 +18,9 @@ class AnalyticsProcessor: the Flagsmith SDK. Docs: https://docs.flagsmith.com/advanced-use/flag-analytics. """ - def __init__(self, environment_key: str, base_api_url: str, timeout: int = 3): + def __init__( + self, environment_key: str, base_api_url: str, timeout: typing.Optional[int] = 3 + ): """ Initialise the AnalyticsProcessor to handle sending analytics on flag usage to the Flagsmith API. @@ -30,10 +33,10 @@ def __init__(self, environment_key: str, base_api_url: str, timeout: int = 3): self.analytics_endpoint = base_api_url + ANALYTICS_ENDPOINT self.environment_key = environment_key self._last_flushed = datetime.now() - self.analytics_data = {} - self.timeout = timeout + self.analytics_data: typing.MutableMapping[str, typing.Any] = {} + self.timeout = timeout or 3 - def flush(self): + def flush(self) -> None: """ Sends all the collected data to the api asynchronously and resets the timer """ @@ -53,7 +56,7 @@ def flush(self): self.analytics_data.clear() self._last_flushed = datetime.now() - def track_feature(self, feature_name: str): + def track_feature(self, feature_name: str) -> None: self.analytics_data[feature_name] = self.analytics_data.get(feature_name, 0) + 1 if (datetime.now() - self._last_flushed).seconds > ANALYTICS_TIMER: self.flush() diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index a600182..3cd4c2b 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -20,13 +20,23 @@ from flagsmith.offline_handlers import BaseOfflineHandler from flagsmith.polling_manager import EnvironmentDataPollingManager from flagsmith.streaming_manager import EventStreamManager, StreamEvent -from flagsmith.utils.identities import generate_identities_data +from flagsmith.utils.identities import Identity, generate_identities_data logger = logging.getLogger(__name__) DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/" DEFAULT_REALTIME_API_URL = "https://realtime.flagsmith.com/" +JsonType = typing.Union[ + None, + int, + str, + bool, + typing.List["JsonType"], + typing.List[typing.Mapping[str, "JsonType"]], + typing.Dict[str, "JsonType"], +] + class Flagsmith: """A Flagsmith client. @@ -45,19 +55,21 @@ class Flagsmith: def __init__( self, - environment_key: str = None, - api_url: str = None, + environment_key: typing.Optional[str] = None, + api_url: typing.Optional[str] = None, realtime_api_url: typing.Optional[str] = None, - custom_headers: typing.Dict[str, typing.Any] = None, - request_timeout_seconds: int = None, + custom_headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_timeout_seconds: typing.Optional[int] = None, enable_local_evaluation: bool = False, environment_refresh_interval_seconds: typing.Union[int, float] = 60, - retries: Retry = None, + retries: typing.Optional[Retry] = None, enable_analytics: bool = False, - default_flag_handler: typing.Callable[[str], DefaultFlag] = None, - proxies: typing.Dict[str, str] = None, + default_flag_handler: typing.Optional[ + typing.Callable[[str], DefaultFlag] + ] = None, + proxies: typing.Optional[typing.Dict[str, str]] = None, offline_mode: bool = False, - offline_handler: BaseOfflineHandler = None, + offline_handler: typing.Optional[BaseOfflineHandler] = None, enable_realtime_updates: bool = False, ): """ @@ -94,8 +106,8 @@ def __init__( self.offline_handler = offline_handler self.default_flag_handler = default_flag_handler self.enable_realtime_updates = enable_realtime_updates - self._analytics_processor = None - self._environment = None + self._analytics_processor: typing.Optional[AnalyticsProcessor] = None + self._environment: typing.Optional[EnvironmentModel] = None self._identity_overrides_by_identifier: typing.Dict[str, IdentityModel] = {} # argument validation @@ -159,6 +171,9 @@ def __init__( def _initialise_local_evaluation(self) -> None: if self.enable_realtime_updates: self.update_environment() + if not self._environment: + raise ValueError("Unable to get environment from API key") + stream_url = f"{self.realtime_api_url}sse/environments/{self._environment.api_key}/stream" self.event_stream_thread = EventStreamManager( @@ -196,6 +211,10 @@ def handle_stream_event(self, event: StreamEvent) -> None: if stream_updated_at.tzinfo is None: stream_updated_at = pytz.utc.localize(stream_updated_at) + if not self._environment: + raise ValueError( + "Unable to access environment. Environment should not be null" + ) environment_updated_at = self._environment.updated_at if environment_updated_at.tzinfo is None: environment_updated_at = pytz.utc.localize(environment_updated_at) @@ -214,7 +233,9 @@ def get_environment_flags(self) -> Flags: return self._get_environment_flags_from_api() def get_identity_flags( - self, identifier: str, traits: typing.Dict[str, typing.Any] = None + self, + identifier: str, + traits: typing.Optional[typing.Mapping[str, TraitValue]] = None, ) -> Flags: """ Get all the flags for the current environment for a given identity. Will also @@ -233,7 +254,9 @@ def get_identity_flags( return self._get_identity_flags_from_api(identifier, traits) def get_identity_segments( - self, identifier: str, traits: typing.Dict[str, typing.Any] = None + self, + identifier: str, + traits: typing.Optional[typing.Mapping[str, TraitValue]] = None, ) -> typing.List[Segment]: """ Get a list of segments that the given identity is in. @@ -255,7 +278,7 @@ def get_identity_segments( segment_models = get_identity_segments(self._environment, identity_model) return [Segment(id=sm.id, name=sm.name) for sm in segment_models] - def update_environment(self): + def update_environment(self) -> None: self._environment = self._get_environment_from_api() self._update_overrides() @@ -272,6 +295,8 @@ def _get_environment_from_api(self) -> EnvironmentModel: return EnvironmentModel.model_validate(environment_data) def _get_environment_flags_from_document(self) -> Flags: + if self._environment is None: + raise TypeError("No environment present") return Flags.from_feature_state_models( feature_states=engine.get_environment_feature_states(self._environment), analytics_processor=self._analytics_processor, @@ -279,9 +304,11 @@ def _get_environment_flags_from_document(self) -> Flags: ) def _get_identity_flags_from_document( - self, identifier: str, traits: typing.Dict[str, typing.Any] + self, identifier: str, traits: typing.Mapping[str, TraitValue] ) -> Flags: identity_model = self._get_identity_model(identifier, **traits) + if self._environment is None: + raise TypeError("No environment present") feature_states = engine.get_identity_feature_states( self._environment, identity_model ) @@ -294,11 +321,11 @@ def _get_identity_flags_from_document( def _get_environment_flags_from_api(self) -> Flags: try: - api_flags = self._get_json_response( - url=self.environment_flags_url, method="GET" - ) + json_response: typing.List[ + typing.Mapping[str, JsonType] + ] = self._get_json_response(url=self.environment_flags_url, method="GET") return Flags.from_api_flags( - api_flags=api_flags, + api_flags=json_response, analytics_processor=self._analytics_processor, default_flag_handler=self.default_flag_handler, ) @@ -310,11 +337,13 @@ def _get_environment_flags_from_api(self) -> Flags: raise def _get_identity_flags_from_api( - self, identifier: str, traits: typing.Dict[str, typing.Any] + self, identifier: str, traits: typing.Mapping[str, typing.Any] ) -> Flags: try: data = generate_identities_data(identifier, traits) - json_response = self._get_json_response( + json_response: typing.Dict[ + str, typing.List[typing.Dict[str, JsonType]] + ] = self._get_json_response( url=self.identities_url, method="POST", body=data ) return Flags.from_api_flags( @@ -329,7 +358,14 @@ def _get_identity_flags_from_api( return Flags(default_flag_handler=self.default_flag_handler) raise - def _get_json_response(self, url: str, method: str, body: dict = None): + def _get_json_response( + self, + url: str, + method: str, + body: typing.Optional[ + typing.Union[Identity, typing.Dict[str, JsonType]] + ] = None, + ) -> typing.Any: try: request_method = getattr(self.session, method.lower()) response = request_method( @@ -371,7 +407,7 @@ def _get_identity_model( identity_traits=trait_models, ) - def __del__(self): + def __del__(self) -> None: if hasattr(self, "environment_data_polling_manager_thread"): self.environment_data_polling_manager_thread.stop() diff --git a/flagsmith/models.py b/flagsmith/models.py index 41d0037..d06a95f 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing from dataclasses import dataclass, field @@ -28,8 +30,8 @@ class Flag(BaseFlag): def from_feature_state_model( cls, feature_state_model: FeatureStateModel, - identity_id: typing.Union[str, int] = None, - ) -> "Flag": + identity_id: typing.Optional[typing.Union[str, int]] = None, + ) -> Flag: return Flag( enabled=feature_state_model.enabled, value=feature_state_model.get_value(identity_id=identity_id), @@ -38,7 +40,7 @@ def from_feature_state_model( ) @classmethod - def from_api_flag(cls, flag_data: dict) -> "Flag": + def from_api_flag(cls, flag_data: typing.Mapping[str, typing.Any]) -> Flag: return Flag( enabled=flag_data["enabled"], value=flag_data["feature_state_value"], @@ -50,17 +52,17 @@ def from_api_flag(cls, flag_data: dict) -> "Flag": @dataclass class Flags: flags: typing.Dict[str, Flag] = field(default_factory=dict) - default_flag_handler: typing.Callable[[str], DefaultFlag] = None - _analytics_processor: AnalyticsProcessor = None + default_flag_handler: typing.Optional[typing.Callable[[str], DefaultFlag]] = None + _analytics_processor: typing.Optional[AnalyticsProcessor] = None @classmethod def from_feature_state_models( cls, - feature_states: typing.List[FeatureStateModel], - analytics_processor: AnalyticsProcessor, - default_flag_handler: typing.Callable, - identity_id: typing.Union[str, int] = None, - ) -> "Flags": + feature_states: typing.Sequence[FeatureStateModel], + analytics_processor: typing.Optional[AnalyticsProcessor], + default_flag_handler: typing.Optional[typing.Callable[[str], DefaultFlag]], + identity_id: typing.Optional[typing.Union[str, int]] = None, + ) -> Flags: flags = { feature_state.feature.name: Flag.from_feature_state_model( feature_state, identity_id=identity_id @@ -77,10 +79,10 @@ def from_feature_state_models( @classmethod def from_api_flags( cls, - api_flags: typing.List[dict], - analytics_processor: AnalyticsProcessor, - default_flag_handler: typing.Callable, - ) -> "Flags": + api_flags: typing.Sequence[typing.Mapping[str, typing.Any]], + analytics_processor: typing.Optional[AnalyticsProcessor], + default_flag_handler: typing.Optional[typing.Callable[[str], DefaultFlag]], + ) -> Flags: flags = { flag_data["feature"]["name"]: Flag.from_api_flag(flag_data) for flag_data in api_flags @@ -120,12 +122,12 @@ def get_feature_value(self, feature_name: str) -> typing.Any: """ return self.get_flag(feature_name).value - def get_flag(self, feature_name: str) -> BaseFlag: + def get_flag(self, feature_name: str) -> typing.Union[DefaultFlag, Flag]: """ Get a specific flag given the feature name. :param feature_name: the name of the feature to retrieve the flag for. - :return: BaseFlag object. + :return: DefaultFlag | Flag object. :raises FlagsmithClientError: if feature doesn't exist """ try: diff --git a/flagsmith/polling_manager.py b/flagsmith/polling_manager.py index e922ea8..85b8486 100644 --- a/flagsmith/polling_manager.py +++ b/flagsmith/polling_manager.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import threading import time @@ -16,10 +18,10 @@ class EnvironmentDataPollingManager(threading.Thread): def __init__( self, - *args, - main: "Flagsmith", + *args: typing.Any, + main: Flagsmith, refresh_interval_seconds: typing.Union[int, float] = 10, - **kwargs + **kwargs: typing.Any, ): super(EnvironmentDataPollingManager, self).__init__(*args, **kwargs) self._stop_event = threading.Event() @@ -37,5 +39,5 @@ def run(self) -> None: def stop(self) -> None: self._stop_event.set() - def __del__(self): + def __del__(self) -> None: self._stop_event.set() diff --git a/flagsmith/py.typed b/flagsmith/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/flagsmith/streaming_manager.py b/flagsmith/streaming_manager.py index 64ac6a7..3fc6817 100644 --- a/flagsmith/streaming_manager.py +++ b/flagsmith/streaming_manager.py @@ -1,5 +1,6 @@ import logging import threading +import typing from typing import Callable, Generator, Optional, Protocol, cast import requests @@ -17,11 +18,11 @@ class StreamEvent(Protocol): class EventStreamManager(threading.Thread): def __init__( self, - *args, + *args: typing.Any, stream_url: str, on_event: Callable[[StreamEvent], None], request_timeout_seconds: Optional[int] = None, - **kwargs + **kwargs: typing.Any ) -> None: super().__init__(*args, **kwargs) self._stop_event = threading.Event() diff --git a/flagsmith/utils/identities.py b/flagsmith/utils/identities.py index 9c82333..79d8875 100644 --- a/flagsmith/utils/identities.py +++ b/flagsmith/utils/identities.py @@ -1,5 +1,19 @@ -def generate_identities_data(identifier: str, traits: dict = None): +import typing + +from flag_engine.identities.traits.types import TraitValue + +Identity = typing.TypedDict( + "Identity", + {"identifier": str, "traits": typing.List[typing.Mapping[str, TraitValue]]}, +) + + +def generate_identities_data( + identifier: str, traits: typing.Optional[typing.Mapping[str, TraitValue]] = None +) -> Identity: return { "identifier": identifier, - "traits": [{"trait_key": k, "trait_value": v} for k, v in traits.items()], + "traits": [{"trait_key": k, "trait_value": v} for k, v in traits.items()] + if traits + else [], } diff --git a/poetry.lock b/poetry.lock index a733f37..c8990ce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -208,63 +208,63 @@ files = [ [[package]] name = "coverage" -version = "7.4.1" +version = "7.4.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, + {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, + {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, + {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, + {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, + {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, + {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, + {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, + {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, + {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, + {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, + {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, + {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, ] [package.dependencies] @@ -331,29 +331,29 @@ semver = ">=3.0.1" [[package]] name = "flake8" -version = "4.0.1" +version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8.1" files = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, ] [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" [[package]] name = "identify" -version = "2.5.33" +version = "2.5.35" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, ] [package.extras] @@ -397,15 +397,62 @@ colors = ["colorama (>=0.4.6)"] [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false -python-versions = "*" +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy" +version = "1.9.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, ] +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -433,13 +480,13 @@ setuptools = "*" [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -503,29 +550,29 @@ virtualenv = ">=20.10.0" [[package]] name = "pycodestyle" -version = "2.8.0" +version = "2.11.1" description = "Python style guide checker" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, ] [[package]] name = "pydantic" -version = "2.6.1" +version = "2.6.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, - {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, + {file = "pydantic-2.6.3-py3-none-any.whl", hash = "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a"}, + {file = "pydantic-2.6.3.tar.gz", hash = "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.2" +pydantic-core = "2.16.3" typing-extensions = ">=4.6.1" [package.extras] @@ -533,13 +580,13 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-collections" -version = "0.5.2" +version = "0.5.4" description = "Collections of pydantic models" optional = false python-versions = "*" files = [ - {file = "pydantic-collections-0.5.2.tar.gz", hash = "sha256:48d1317e55342e3df6403a900e8a326d3c8452300c3a9c29e1cf032e09409454"}, - {file = "pydantic_collections-0.5.2-py3-none-any.whl", hash = "sha256:5540c645759e5d52b56b181639c22748936c04d3850cfaa2ac04f2c7169698ba"}, + {file = "pydantic-collections-0.5.4.tar.gz", hash = "sha256:5bce65519456b4829f918c2456d58aac3620a866603461a702aafffe08845966"}, + {file = "pydantic_collections-0.5.4-py3-none-any.whl", hash = "sha256:5d107170c89fb17de229f5e8c4b4355af27594444fd0f93086048ccafa69238b"}, ] [package.dependencies] @@ -548,90 +595,90 @@ typing-extensions = ">=4.7.1" [[package]] name = "pydantic-core" -version = "2.16.2" +version = "2.16.3" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, - {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, - {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, - {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, - {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, - {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, - {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, - {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, - {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, - {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, - {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, - {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, - {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, - {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, ] [package.dependencies] @@ -639,13 +686,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyflakes" -version = "2.4.0" +version = "3.1.0" description = "passive checker of Python programs" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, ] [[package]] @@ -815,23 +862,22 @@ dev = ["black (>=22.3.0)", "build (>=0.7.0)", "isort (>=5.11.4)", "pyflakes (>=2 [[package]] name = "responses" -version = "0.23.3" +version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "responses-0.23.3-py3-none-any.whl", hash = "sha256:e6fbcf5d82172fecc0aa1860fd91e58cbfd96cee5e96da5b63fa6eb3caa10dd3"}, - {file = "responses-0.23.3.tar.gz", hash = "sha256:205029e1cb334c21cb4ec64fc7599be48b859a0fd381a42443cdd600bfe8b16a"}, + {file = "responses-0.24.1-py3-none-any.whl", hash = "sha256:a2b43f4c08bfb9c9bd242568328c65a34b318741d3fab884ac843c5ceeb543f9"}, + {file = "responses-0.24.1.tar.gz", hash = "sha256:b127c6ca3f8df0eb9cc82fd93109a3007a86acb24871834c47b77765152ecf8c"}, ] [package.dependencies] pyyaml = "*" requests = ">=2.30.0,<3.0" -types-PyYAML = "*" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "semver" @@ -846,19 +892,19 @@ files = [ [[package]] name = "setuptools" -version = "69.0.3" +version = "69.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "sseclient-py" @@ -883,36 +929,50 @@ files = [ ] [[package]] -name = "types-pyyaml" -version = "6.0.12.12" -description = "Typing stubs for PyYAML" +name = "types-pytz" +version = "2024.1.0.20240203" +description = "Typing stubs for pytz" optional = false -python-versions = "*" +python-versions = ">=3.8" +files = [ + {file = "types-pytz-2024.1.0.20240203.tar.gz", hash = "sha256:c93751ee20dfc6e054a0148f8f5227b9a00b79c90a4d3c9f464711a73179c89e"}, + {file = "types_pytz-2024.1.0.20240203-py3-none-any.whl", hash = "sha256:9679eef0365db3af91ef7722c199dbb75ee5c1b67e3c4dd7bfbeb1b8a71c21a3"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.20240218" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" files = [ - {file = "types-PyYAML-6.0.12.12.tar.gz", hash = "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062"}, - {file = "types_PyYAML-6.0.12.12-py3-none-any.whl", hash = "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"}, + {file = "types-requests-2.31.0.20240218.tar.gz", hash = "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"}, + {file = "types_requests-2.31.0.20240218-py3-none-any.whl", hash = "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b"}, ] +[package.dependencies] +urllib3 = ">=2" + [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] @@ -923,13 +983,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.25.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, + {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, ] [package.dependencies] @@ -943,5 +1003,5 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" -python-versions = ">=3.8.0,<4" -content-hash = "2b957eb89c6fb847554280ba8991651b5e2d79a5f42836bfc58b70312a7c5dde" +python-versions = ">=3.8.1,<4" +content-hash = "98662db30ac17a633b8b5a92816f464847c1c48d68f57842863af90444605b25" diff --git a/pyproject.toml b/pyproject.toml index a58a75d..a6558c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,25 +10,33 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -python = ">=3.8.0,<4" +python = ">=3.8.1,<4" requests = "^2.27.1" requests-futures = "^1.0.0" flagsmith-flag-engine = "^5.1.0" sseclient-py = "^1.8.0" pytz = "^2023.4" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev] +optional = true + +[tool.poetry.group.dev.dependencies] pytest = "^7.4.0" pytest-mock = "^3.6.1" black = "^23.3.0" pre-commit = "^2.17.0" -responses = "^0.23.3" -flake8 = "^4.0.1" -isort = "^5.10.1" - -[tool.poetry.group.dev.dependencies] -pytest = "^7.4.0" +responses = "^0.24.1" +flake8 = "^6.1.0" +isort = "^5.12.0" +mypy = "^1.7.1" +types-requests = "^2.31.0.10" pytest-cov = "^4.1.0" +types-pytz = "^2024.1.0.20240203" + +[tool.mypy] +plugins = ["pydantic.mypy"] +exclude = ["example/*"] + [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/conftest.py b/tests/conftest.py index d13c761..0d28bf2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ import os import random import string +import typing from typing import Generator import pytest @@ -16,37 +17,35 @@ @pytest.fixture() -def analytics_processor(): +def analytics_processor() -> AnalyticsProcessor: return AnalyticsProcessor( environment_key="test_key", base_api_url="http://test_url" ) @pytest.fixture(scope="session") -def api_key(): +def api_key() -> str: return "".join(random.sample(string.ascii_letters, 20)) @pytest.fixture(scope="session") -def server_api_key(): +def server_api_key() -> str: return "ser.%s" % "".join(random.sample(string.ascii_letters, 20)) @pytest.fixture() -def flagsmith(api_key): +def flagsmith(api_key: str) -> Flagsmith: return Flagsmith(environment_key=api_key) @pytest.fixture() -def environment_json(): +def environment_json() -> typing.Generator[str, None, None]: with open(os.path.join(DATA_DIR, "environment.json"), "rt") as f: yield f.read() @pytest.fixture() -def requests_session_response_ok( - mocker: Generator[MockerFixture, None, None], environment_json: str -) -> None: +def requests_session_response_ok(mocker: MockerFixture, environment_json: str) -> None: mock_session = mocker.MagicMock() mocker.patch("flagsmith.flagsmith.requests.Session", return_value=mock_session) @@ -76,13 +75,13 @@ def environment_model(environment_json: str) -> EnvironmentModel: @pytest.fixture() -def flags_json(): +def flags_json() -> typing.Generator[str, None, None]: with open(os.path.join(DATA_DIR, "flags.json"), "rt") as f: yield f.read() @pytest.fixture() -def identities_json(): +def identities_json() -> typing.Generator[str, None, None]: with open(os.path.join(DATA_DIR, "identities.json"), "rt") as f: yield f.read() diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 60126b4..7a2fd65 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -2,10 +2,12 @@ from datetime import datetime, timedelta from unittest import mock -from flagsmith.analytics import ANALYTICS_TIMER +from flagsmith.analytics import ANALYTICS_TIMER, AnalyticsProcessor -def test_analytics_processor_track_feature_updates_analytics_data(analytics_processor): +def test_analytics_processor_track_feature_updates_analytics_data( + analytics_processor: AnalyticsProcessor, +) -> None: # When analytics_processor.track_feature("my_feature") assert analytics_processor.analytics_data["my_feature"] == 1 @@ -14,15 +16,17 @@ def test_analytics_processor_track_feature_updates_analytics_data(analytics_proc assert analytics_processor.analytics_data["my_feature"] == 2 -def test_analytics_processor_flush_clears_analytics_data(analytics_processor): +def test_analytics_processor_flush_clears_analytics_data( + analytics_processor: AnalyticsProcessor, +) -> None: analytics_processor.track_feature("my_feature") analytics_processor.flush() assert analytics_processor.analytics_data == {} def test_analytics_processor_flush_post_request_data_match_ananlytics_data( - analytics_processor, -): + analytics_processor: AnalyticsProcessor, +) -> None: # Given with mock.patch("flagsmith.analytics.session") as session: # When @@ -36,8 +40,8 @@ def test_analytics_processor_flush_post_request_data_match_ananlytics_data( def test_analytics_processor_flush_early_exit_if_analytics_data_is_empty( - analytics_processor, -): + analytics_processor: AnalyticsProcessor, +) -> None: with mock.patch("flagsmith.analytics.session") as session: analytics_processor.flush() @@ -46,8 +50,8 @@ def test_analytics_processor_flush_early_exit_if_analytics_data_is_empty( def test_analytics_processor_calling_track_feature_calls_flush_when_timer_runs_out( - analytics_processor, -): + analytics_processor: AnalyticsProcessor, +) -> None: # Given with mock.patch("flagsmith.analytics.datetime") as mocked_datetime, mock.patch( "flagsmith.analytics.session" diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 9d0d23c..f6afdbc 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -6,19 +6,19 @@ import pytest import requests import responses +from flag_engine.environments.models import EnvironmentModel from flag_engine.features.models import FeatureModel, FeatureStateModel +from pytest_mock import MockerFixture from flagsmith import Flagsmith from flagsmith.exceptions import FlagsmithAPIError from flagsmith.models import DefaultFlag, Flags from flagsmith.offline_handlers import BaseOfflineHandler -if typing.TYPE_CHECKING: - from flag_engine.environments.models import EnvironmentModel - from pytest_mock import MockerFixture - -def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, server_api_key): +def test_flagsmith_starts_polling_manager_on_init_if_enabled( + mocker: MockerFixture, server_api_key: str +) -> None: # Given mock_polling_manager = mocker.MagicMock() mocker.patch( @@ -35,8 +35,8 @@ def test_flagsmith_starts_polling_manager_on_init_if_enabled(mocker, server_api_ @responses.activate() def test_update_environment_sets_environment( - flagsmith, environment_json, environment_model -): + flagsmith: Flagsmith, environment_json: str, environment_model: EnvironmentModel +) -> None: # Given responses.add(method="GET", url=flagsmith.environment_url, body=environment_json) assert flagsmith._environment is None @@ -51,8 +51,8 @@ def test_update_environment_sets_environment( @responses.activate() def test_get_environment_flags_calls_api_when_no_local_environment( - api_key, flagsmith, flags_json -): + api_key: str, flagsmith: Flagsmith, flags_json: str +) -> None: # Given responses.add(method="GET", url=flagsmith.environment_flags_url, body=flags_json) @@ -71,8 +71,8 @@ def test_get_environment_flags_calls_api_when_no_local_environment( @responses.activate() def test_get_environment_flags_uses_local_environment_when_available( - flagsmith, environment_model -): + flagsmith: Flagsmith, environment_model: EnvironmentModel +) -> None: # Given flagsmith._environment = environment_model flagsmith.enable_local_evaluation = True @@ -90,8 +90,8 @@ def test_get_environment_flags_uses_local_environment_when_available( @responses.activate() def test_get_identity_flags_calls_api_when_no_local_environment_no_traits( - flagsmith, identities_json -): + flagsmith: Flagsmith, identities_json: str +) -> None: # Given responses.add(method="POST", url=flagsmith.identities_url, body=identities_json) identifier = "identifier" @@ -100,9 +100,11 @@ def test_get_identity_flags_calls_api_when_no_local_environment_no_traits( identity_flags = flagsmith.get_identity_flags(identifier=identifier).all_flags() # Then - assert responses.calls[0].request.body.decode() == json.dumps( - {"identifier": identifier, "traits": []} - ) + body = responses.calls[0].request.body + if isinstance(body, bytes): + # Decode 'body' from bytes to string if it is in bytes format. + body = body.decode() + assert body == json.dumps({"identifier": identifier, "traits": []}) # Taken from hard coded values in tests/data/identities.json assert identity_flags[0].enabled is True @@ -112,8 +114,8 @@ def test_get_identity_flags_calls_api_when_no_local_environment_no_traits( @responses.activate() def test_get_identity_flags_calls_api_when_no_local_environment_with_traits( - flagsmith, identities_json -): + flagsmith: Flagsmith, identities_json: str +) -> None: # Given responses.add(method="POST", url=flagsmith.identities_url, body=identities_json) identifier = "identifier" @@ -123,7 +125,11 @@ def test_get_identity_flags_calls_api_when_no_local_environment_with_traits( identity_flags = flagsmith.get_identity_flags(identifier=identifier, traits=traits) # Then - assert responses.calls[0].request.body.decode() == json.dumps( + body = responses.calls[0].request.body + if isinstance(body, bytes): + # Decode 'body' from bytes to string if it is in bytes format. + body = body.decode() + assert body == json.dumps( { "identifier": identifier, "traits": [{"trait_key": k, "trait_value": v} for k, v in traits.items()], @@ -138,8 +144,8 @@ def test_get_identity_flags_calls_api_when_no_local_environment_with_traits( @responses.activate() def test_get_identity_flags_uses_local_environment_when_available( - flagsmith, environment_model, mocker -): + flagsmith: Flagsmith, environment_model: EnvironmentModel, mocker: MockerFixture +) -> None: # Given flagsmith._environment = environment_model flagsmith.enable_local_evaluation = True @@ -163,7 +169,9 @@ def test_get_identity_flags_uses_local_environment_when_available( assert identity_flags[0].value == feature_state.get_value() -def test_request_connection_error_raises_flagsmith_api_error(mocker, api_key): +def test_request_connection_error_raises_flagsmith_api_error( + mocker: MockerFixture, api_key: str +) -> None: """ Test the behaviour when session. raises a ConnectionError. Note that this does not account for the fact that we are using retries. Since this is a standard @@ -188,7 +196,7 @@ def test_request_connection_error_raises_flagsmith_api_error(mocker, api_key): @responses.activate() -def test_non_200_response_raises_flagsmith_api_error(flagsmith): +def test_non_200_response_raises_flagsmith_api_error(flagsmith: Flagsmith) -> None: # Given responses.add(url=flagsmith.environment_flags_url, method="GET", status=400) @@ -201,7 +209,7 @@ def test_non_200_response_raises_flagsmith_api_error(flagsmith): @responses.activate() -def test_default_flag_is_used_when_no_environment_flags_returned(api_key): +def test_default_flag_is_used_when_no_environment_flags_returned(api_key: str) -> None: # Given feature_name = "some_feature" @@ -232,7 +240,9 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: @responses.activate() -def test_default_flag_is_not_used_when_environment_flags_returned(api_key, flags_json): +def test_default_flag_is_not_used_when_environment_flags_returned( + api_key: str, flags_json: str +) -> None: # Given feature_name = "some_feature" @@ -261,7 +271,7 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: @responses.activate() -def test_default_flag_is_used_when_no_identity_flags_returned(api_key): +def test_default_flag_is_used_when_no_identity_flags_returned(api_key: str) -> None: # Given feature_name = "some_feature" @@ -276,7 +286,10 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: ) # and we mock the API to return an empty list of flags - response_data = {"flags": [], "traits": []} + response_data: typing.Mapping[str, typing.Sequence[typing.Any]] = { + "flags": [], + "traits": [], + } responses.add( url=flagsmith.identities_url, method="POST", body=json.dumps(response_data) ) @@ -294,8 +307,8 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: @responses.activate() def test_default_flag_is_not_used_when_identity_flags_returned( - api_key, identities_json -): + api_key: str, identities_json: str +) -> None: # Given feature_name = "some_feature" @@ -323,7 +336,9 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: assert flag.value == "some-value" # hard coded value in tests/data/identities.json -def test_default_flags_are_used_if_api_error_and_default_flag_handler_given(mocker): +def test_default_flags_are_used_if_api_error_and_default_flag_handler_given( + mocker: MockerFixture, +) -> None: # Given # a default flag and associated handler default_flag = DefaultFlag(True, "some-default-value") @@ -347,7 +362,9 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: assert flags.get_flag("some-feature") == default_flag -def test_get_identity_segments_no_traits(local_eval_flagsmith, environment_model): +def test_get_identity_segments_no_traits( + local_eval_flagsmith: Flagsmith, environment_model: EnvironmentModel +) -> None: # Given identifier = "identifier" @@ -359,8 +376,8 @@ def test_get_identity_segments_no_traits(local_eval_flagsmith, environment_model def test_get_identity_segments_with_valid_trait( - local_eval_flagsmith, environment_model -): + local_eval_flagsmith: Flagsmith, environment_model: EnvironmentModel +) -> None: # Given identifier = "identifier" traits = {"foo": "bar"} # obtained from data/environment.json @@ -373,12 +390,12 @@ def test_get_identity_segments_with_valid_trait( assert segments[0].name == "Test segment" # obtained from data/environment.json -def test_local_evaluation_requires_server_key(): +def test_local_evaluation_requires_server_key() -> None: with pytest.raises(ValueError): Flagsmith(environment_key="not-a-server-key", enable_local_evaluation=True) -def test_initialise_flagsmith_with_proxies(): +def test_initialise_flagsmith_with_proxies() -> None: # Given proxies = {"https": "https://my.proxy.com/proxy-me"} @@ -389,10 +406,10 @@ def test_initialise_flagsmith_with_proxies(): assert flagsmith.session.proxies == proxies -def test_offline_mode(environment_model: "EnvironmentModel") -> None: +def test_offline_mode(environment_model: EnvironmentModel) -> None: # Given class DummyOfflineHandler(BaseOfflineHandler): - def get_environment(self) -> "EnvironmentModel": + def get_environment(self) -> EnvironmentModel: return environment_model # When @@ -409,7 +426,7 @@ def get_environment(self) -> "EnvironmentModel": @responses.activate() def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( - mocker: "MockerFixture", environment_model: "EnvironmentModel" + mocker: MockerFixture, environment_model: EnvironmentModel ) -> None: # Given api_url = "http://some.flagsmith.com/api/v1/" @@ -439,7 +456,7 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( assert identity_flags.get_feature_value("some_feature") == "some-value" -def test_cannot_use_offline_mode_without_offline_handler(): +def test_cannot_use_offline_mode_without_offline_handler() -> None: with pytest.raises(ValueError) as e: # When Flagsmith(offline_mode=True, offline_handler=None) @@ -451,7 +468,7 @@ def test_cannot_use_offline_mode_without_offline_handler(): ) -def test_cannot_use_default_handler_and_offline_handler(mocker): +def test_cannot_use_default_handler_and_offline_handler(mocker: MockerFixture) -> None: # When with pytest.raises(ValueError) as e: Flagsmith( @@ -468,7 +485,7 @@ def test_cannot_use_default_handler_and_offline_handler(mocker): ) -def test_cannot_create_flagsmith_client_in_remote_evaluation_without_api_key(): +def test_cannot_create_flagsmith_client_in_remote_evaluation_without_api_key() -> None: # When with pytest.raises(ValueError) as e: Flagsmith() diff --git a/tests/test_offline_handlers.py b/tests/test_offline_handlers.py index 862f173..b3cc597 100644 --- a/tests/test_offline_handlers.py +++ b/tests/test_offline_handlers.py @@ -5,7 +5,7 @@ from flagsmith.offline_handlers import LocalFileHandler -def test_local_file_handler(environment_json): +def test_local_file_handler(environment_json: str) -> None: with patch("builtins.open", mock_open(read_data=environment_json)) as mock_file: # Given environment_document_file_path = "/some/path/environment.json" diff --git a/tests/test_polling_manager.py b/tests/test_polling_manager.py index 6cfcffd..c30df6f 100644 --- a/tests/test_polling_manager.py +++ b/tests/test_polling_manager.py @@ -2,12 +2,13 @@ from unittest import mock import requests +from pytest_mock import MockerFixture from flagsmith import Flagsmith from flagsmith.polling_manager import EnvironmentDataPollingManager -def test_polling_manager_calls_update_environment_on_start(): +def test_polling_manager_calls_update_environment_on_start() -> None: # Given flagsmith = mock.MagicMock() polling_manager = EnvironmentDataPollingManager( @@ -22,7 +23,7 @@ def test_polling_manager_calls_update_environment_on_start(): polling_manager.stop() -def test_polling_manager_calls_update_environment_on_each_refresh(): +def test_polling_manager_calls_update_environment_on_each_refresh() -> None: # Given flagsmith = mock.MagicMock() polling_manager = EnvironmentDataPollingManager( @@ -40,7 +41,9 @@ def test_polling_manager_calls_update_environment_on_each_refresh(): polling_manager.stop() -def test_polling_manager_is_resilient_to_api_errors(mocker, server_api_key): +def test_polling_manager_is_resilient_to_api_errors( + mocker: MockerFixture, server_api_key: str +) -> None: # Given session_mock = mocker.patch("requests.Session") session_mock.get.return_value = mock.MagicMock(status_code=500) @@ -56,7 +59,9 @@ def test_polling_manager_is_resilient_to_api_errors(mocker, server_api_key): polling_manager.stop() -def test_polling_manager_is_resilient_to_request_exceptions(mocker, server_api_key): +def test_polling_manager_is_resilient_to_request_exceptions( + mocker: MockerFixture, server_api_key: str +) -> None: # Given session_mock = mocker.patch("requests.Session") session_mock.get.side_effect = requests.RequestException() diff --git a/tests/test_streaming_manager.py b/tests/test_streaming_manager.py index 136f40b..bd4bf93 100644 --- a/tests/test_streaming_manager.py +++ b/tests/test_streaming_manager.py @@ -1,6 +1,5 @@ import time from datetime import datetime -from typing import Generator from unittest.mock import MagicMock, Mock import pytest @@ -14,7 +13,7 @@ def test_stream_manager_handles_timeout( - mocked_responses: Generator["responses.RequestsMock", None, None] + mocked_responses: responses.RequestsMock, ) -> None: stream_url = ( "https://realtime.flagsmith.com/sse/environments/B62qaMZNwfiqT76p38ggrQ/stream" @@ -38,8 +37,8 @@ def test_stream_manager_handles_timeout( def test_stream_manager_handles_request_exception( - mocked_responses: Generator["responses.RequestsMock", None, None], - caplog: Generator["pytest.LogCaptureFixture", None, None], + mocked_responses: responses.RequestsMock, + caplog: pytest.LogCaptureFixture, ) -> None: stream_url = ( "https://realtime.flagsmith.com/sse/environments/B62qaMZNwfiqT76p38ggrQ/stream" @@ -78,13 +77,12 @@ def test_environment_updates_on_recent_event( flagsmith = Flagsmith(environment_key=server_api_key) flagsmith._environment = MagicMock() flagsmith._environment.updated_at = environment_updated_at - flagsmith.handle_stream_event( event=Mock( data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', ) ) - + assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_called_once() @@ -105,7 +103,7 @@ def test_environment_does_not_update_on_past_event( data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', ) ) - + assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_not_called() @@ -126,7 +124,7 @@ def test_environment_does_not_update_on_same_event( data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', ) ) - + assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_not_called() From 398be6c4eb0fa467d37241372a507e3114fad589 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Thu, 14 Mar 2024 08:46:21 +0000 Subject: [PATCH 037/121] Run pytest on push to main (#78) --- .github/workflows/pytest.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 5e01d0c..1aa686f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,7 +1,10 @@ name: Linting and Tests on: - - pull_request + pull_request: + push: + branches: + - main jobs: test: From 5fc5559858d35730275206af1c8c7ef60aa072b4 Mon Sep 17 00:00:00 2001 From: Zach Aysan Date: Thu, 14 Mar 2024 05:01:15 -0400 Subject: [PATCH 038/121] fix: Set the environment for local evaluation mode on init (#76) * Set the environment for local evaluation mode on init * Make tests pass with the update environment change * Fix test failure * Make update environment update local document * Use responses for polling manager tests * Lint --------- Co-authored-by: Matthew Elwell --- flagsmith/flagsmith.py | 3 +++ tests/test_flagsmith.py | 4 +++- tests/test_polling_manager.py | 28 ++++++++++++++++++++++------ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 3cd4c2b..bbfd227 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -185,6 +185,9 @@ def _initialise_local_evaluation(self) -> None: self.event_stream_thread.start() else: + # To ensure that the environment is set before allowing subsequent + # method calls, update the environment manually. + self.update_environment() self.environment_data_polling_manager_thread = ( EnvironmentDataPollingManager( main=self, diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index f6afdbc..3ecc0fc 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -17,7 +17,9 @@ def test_flagsmith_starts_polling_manager_on_init_if_enabled( - mocker: MockerFixture, server_api_key: str + mocker: MockerFixture, + server_api_key: str, + requests_session_response_ok: None, ) -> None: # Given mock_polling_manager = mocker.MagicMock() diff --git a/tests/test_polling_manager.py b/tests/test_polling_manager.py index c30df6f..e5ccb2d 100644 --- a/tests/test_polling_manager.py +++ b/tests/test_polling_manager.py @@ -2,6 +2,7 @@ from unittest import mock import requests +import responses from pytest_mock import MockerFixture from flagsmith import Flagsmith @@ -41,17 +42,22 @@ def test_polling_manager_calls_update_environment_on_each_refresh() -> None: polling_manager.stop() +@responses.activate() def test_polling_manager_is_resilient_to_api_errors( - mocker: MockerFixture, server_api_key: str + flagsmith: Flagsmith, + environment_json: str, + mocker: MockerFixture, + server_api_key: str, ) -> None: # Given - session_mock = mocker.patch("requests.Session") - session_mock.get.return_value = mock.MagicMock(status_code=500) + responses.add(method="GET", url=flagsmith.environment_url, body=environment_json) flagsmith = Flagsmith( environment_key=server_api_key, enable_local_evaluation=True, environment_refresh_interval_seconds=0.1, ) + + responses.add(method="GET", url=flagsmith.environment_url, status=500) polling_manager = flagsmith.environment_data_polling_manager_thread # Then @@ -59,17 +65,27 @@ def test_polling_manager_is_resilient_to_api_errors( polling_manager.stop() +@responses.activate() def test_polling_manager_is_resilient_to_request_exceptions( - mocker: MockerFixture, server_api_key: str + flagsmith: Flagsmith, + environment_json: str, + mocker: MockerFixture, + server_api_key: str, ) -> None: # Given - session_mock = mocker.patch("requests.Session") - session_mock.get.side_effect = requests.RequestException() + responses.add(method="GET", url=flagsmith.environment_url, body=environment_json) flagsmith = Flagsmith( environment_key=server_api_key, enable_local_evaluation=True, environment_refresh_interval_seconds=0.1, ) + + responses.add( + method="GET", + url=flagsmith.environment_url, + body=requests.RequestException("Some exception"), + status=500, + ) polling_manager = flagsmith.environment_data_polling_manager_thread # Then From 069373f7db2344586d64dcba567ef896add29efc Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Thu, 14 Mar 2024 09:43:29 +0000 Subject: [PATCH 039/121] Bump version 3.6.0 (#79) --- poetry.lock | 10 ---------- pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index c8990ce..5bc2e59 100644 --- a/poetry.lock +++ b/poetry.lock @@ -775,7 +775,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -783,15 +782,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -808,7 +800,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -816,7 +807,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, diff --git a/pyproject.toml b/pyproject.toml index a6558c8..a5bea6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.5.0" +version = "3.6.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From c187ef35be81ca3de5b6284e84115a030a21a7f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:11:45 +0000 Subject: [PATCH 040/121] Bump black from 23.12.1 to 24.3.0 (#81) * Bump black from 23.12.1 to 24.3.0 Bumps [black](https://github.com/psf/black) from 23.12.1 to 24.3.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/23.12.1...24.3.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] * dependabot/pip/black 24.3.0 * dependabot/pip/black 24.3.0 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ben Rometsch --- .pre-commit-config.yaml | 2 +- flagsmith/flagsmith.py | 14 ++++---- flagsmith/utils/identities.py | 8 +++-- poetry.lock | 61 +++++++++++++++++++++-------------- pyproject.toml | 2 +- 5 files changed, 50 insertions(+), 37 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 857ce3a..fb8d96b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 24.3.0 hooks: - id: black language_version: python3 diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index bbfd227..83c2ae4 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -324,9 +324,9 @@ def _get_identity_flags_from_document( def _get_environment_flags_from_api(self) -> Flags: try: - json_response: typing.List[ - typing.Mapping[str, JsonType] - ] = self._get_json_response(url=self.environment_flags_url, method="GET") + json_response: typing.List[typing.Mapping[str, JsonType]] = ( + self._get_json_response(url=self.environment_flags_url, method="GET") + ) return Flags.from_api_flags( api_flags=json_response, analytics_processor=self._analytics_processor, @@ -344,10 +344,10 @@ def _get_identity_flags_from_api( ) -> Flags: try: data = generate_identities_data(identifier, traits) - json_response: typing.Dict[ - str, typing.List[typing.Dict[str, JsonType]] - ] = self._get_json_response( - url=self.identities_url, method="POST", body=data + json_response: typing.Dict[str, typing.List[typing.Dict[str, JsonType]]] = ( + self._get_json_response( + url=self.identities_url, method="POST", body=data + ) ) return Flags.from_api_flags( api_flags=json_response["flags"], diff --git a/flagsmith/utils/identities.py b/flagsmith/utils/identities.py index 79d8875..082721d 100644 --- a/flagsmith/utils/identities.py +++ b/flagsmith/utils/identities.py @@ -13,7 +13,9 @@ def generate_identities_data( ) -> Identity: return { "identifier": identifier, - "traits": [{"trait_key": k, "trait_value": v} for k, v in traits.items()] - if traits - else [], + "traits": ( + [{"trait_key": k, "trait_value": v} for k, v in traits.items()] + if traits + else [] + ), } diff --git a/poetry.lock b/poetry.lock index 5bc2e59..f04891c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -16,33 +16,33 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "black" -version = "23.12.1" +version = "24.3.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, - {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, - {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, - {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, - {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, - {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, - {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, - {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, - {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, - {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, - {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, - {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, - {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, - {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, - {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, - {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, - {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, - {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, - {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, - {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, - {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, - {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, + {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, + {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, + {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, + {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, + {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, + {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, + {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, + {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, + {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, + {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, + {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, + {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, + {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, + {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, + {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, + {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, + {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, + {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, + {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, + {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, + {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, + {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, ] [package.dependencies] @@ -775,6 +775,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -782,8 +783,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -800,6 +809,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -807,6 +817,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -994,4 +1005,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "98662db30ac17a633b8b5a92816f464847c1c48d68f57842863af90444605b25" +content-hash = "4fe3d98e1321fa3251515d35d8ef180169b23e8143b99cda199afdc6bc84339f" diff --git a/pyproject.toml b/pyproject.toml index a5bea6e..79e201d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ optional = true [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" pytest-mock = "^3.6.1" -black = "^23.3.0" +black = ">=23.3,<25.0" pre-commit = "^2.17.0" responses = "^0.24.1" flake8 = "^6.1.0" From 0ebc3971c88b92fb1a399712114f7cbf442d9348 Mon Sep 17 00:00:00 2001 From: Ben Rometsch Date: Tue, 26 Mar 2024 11:12:47 +0000 Subject: [PATCH 041/121] chore/update github actions (#82) --- .github/workflows/publish.yml | 2 +- .github/workflows/pytest.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1ee6bfd..898081d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Cloning repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 1aa686f..a70a392 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -18,12 +18,12 @@ jobs: steps: - name: Cloning repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} From 1e89eed4431f5420a6a65cd4f2a96c4772860833 Mon Sep 17 00:00:00 2001 From: Merijn Bol Date: Fri, 5 Apr 2024 05:23:01 +0200 Subject: [PATCH 042/121] Remove pytz and replace usage with core python modules (#80) Pytz is deprecated since Python 3.9. You might want to keep it for backwards compatibility for Python 3.8. However, turning naive datetimes into UTC timezone datetimes seems also possible in Python 3.8 without using the pytc library. --- .pre-commit-config.yaml | 2 +- flagsmith/flagsmith.py | 7 +++---- poetry.lock | 24 +----------------------- pyproject.toml | 2 -- 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb8d96b..d56e9ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: mypy args: [--strict] additional_dependencies: - [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, types-pytz, sseclient-py] + [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 83c2ae4..0245b7a 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -1,9 +1,8 @@ import json import logging import typing -from datetime import datetime +from datetime import datetime, timezone -import pytz import requests from flag_engine import engine from flag_engine.environments.models import EnvironmentModel @@ -212,7 +211,7 @@ def handle_stream_event(self, event: StreamEvent) -> None: ) from e if stream_updated_at.tzinfo is None: - stream_updated_at = pytz.utc.localize(stream_updated_at) + stream_updated_at = stream_updated_at.astimezone(timezone.utc) if not self._environment: raise ValueError( @@ -220,7 +219,7 @@ def handle_stream_event(self, event: StreamEvent) -> None: ) environment_updated_at = self._environment.updated_at if environment_updated_at.tzinfo is None: - environment_updated_at = pytz.utc.localize(environment_updated_at) + environment_updated_at = environment_updated_at.astimezone(timezone.utc) if stream_updated_at > environment_updated_at: self.update_environment() diff --git a/poetry.lock b/poetry.lock index f04891c..9999e26 100644 --- a/poetry.lock +++ b/poetry.lock @@ -752,17 +752,6 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] -[[package]] -name = "pytz" -version = "2023.4" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, - {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, -] - [[package]] name = "pyyaml" version = "6.0.1" @@ -929,17 +918,6 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "types-pytz" -version = "2024.1.0.20240203" -description = "Typing stubs for pytz" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-pytz-2024.1.0.20240203.tar.gz", hash = "sha256:c93751ee20dfc6e054a0148f8f5227b9a00b79c90a4d3c9f464711a73179c89e"}, - {file = "types_pytz-2024.1.0.20240203-py3-none-any.whl", hash = "sha256:9679eef0365db3af91ef7722c199dbb75ee5c1b67e3c4dd7bfbeb1b8a71c21a3"}, -] - [[package]] name = "types-requests" version = "2.31.0.20240218" @@ -1005,4 +983,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "4fe3d98e1321fa3251515d35d8ef180169b23e8143b99cda199afdc6bc84339f" +content-hash = "555fbec9cbe8ecb76b900ee4958ed71b134f15d4c22d4c9db1c73a9c459306a8" diff --git a/pyproject.toml b/pyproject.toml index 79e201d..c6dfc68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ requests = "^2.27.1" requests-futures = "^1.0.0" flagsmith-flag-engine = "^5.1.0" sseclient-py = "^1.8.0" -pytz = "^2023.4" [tool.poetry.group.dev] optional = true @@ -31,7 +30,6 @@ isort = "^5.12.0" mypy = "^1.7.1" types-requests = "^2.31.0.10" pytest-cov = "^4.1.0" -types-pytz = "^2024.1.0.20240203" [tool.mypy] plugins = ["pydantic.mypy"] From f054e5772a3d0c5b93d9d45337d2721dfeaa60e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Tue, 23 Apr 2024 12:03:42 -0300 Subject: [PATCH 043/121] docs: misc README improvements * Rename Readme.md to standard README.md * Add CONTRIBUTING.md in this repository instead of an external gist * Link to SDK docs with Python already selected --- CONTRIBUTING.md | 17 +++++++++++++++++ Readme.md => README.md | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md rename Readme.md => README.md (80%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..87bd0e6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing + +We're always looking to improve this project, open source contribution is encouraged so long as they adhere to these +guidelines. + +## Pull Requests + +The Flagsmith team will be monitoring for pull requests. When we get one, a member of team will test the work against +our internal uses and sign off on the changes. From here, we'll either merge the pull request or provide feedback +suggesting the next steps. + +### A couple things to keep in mind + +- If you've changed APIs, update the documentation. +- Keep the code style (indents, wrapping) consistent. +- If your PR involves a lot of commits, squash them using `git rebase -i` as this makes it easier for us to review. +- Keep lines under 80 characters. diff --git a/Readme.md b/README.md similarity index 80% rename from Readme.md rename to README.md index addfa5c..2df3a1b 100644 --- a/Readme.md +++ b/README.md @@ -10,11 +10,11 @@ The SDK for Python applications for [https://www.flagsmith.com/](https://www.fla ## Adding to your project For full documentation visit -[https://docs.flagsmith.com/clients/server-side](https://docs.flagsmith.com/clients/server-side). +[https://docs.flagsmith.com/clients/server-side?language=python](https://docs.flagsmith.com/clients/server-side?language=python). ## Contributing -Please read [CONTRIBUTING.md](https://gist.github.com/kyle-ssg/c36a03aebe492e45cbd3eefb21cb0486) for details on our code +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests ## Getting Help From 61e411de394db01bbf3ef138ff93f727030c84e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:21:32 +0100 Subject: [PATCH 044/121] Bump idna from 3.6 to 3.7 (#83) Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7) --- updated-dependencies: - dependency-name: idna dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9999e26..b80077c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -361,13 +361,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] From 3271805703d1a39f29fc3256183df4f5316d53e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 21:22:04 +0100 Subject: [PATCH 045/121] --- (#85) updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index b80077c..188f9c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -814,13 +814,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, + {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, ] [package.dependencies] From a3bf402a44f7f42b3d0296315c68aa1c97bfbdd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 21:23:15 +0100 Subject: [PATCH 046/121] Bump urllib3 from 2.2.1 to 2.2.2 (#87) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.1 to 2.2.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.2.1...2.2.2) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 188f9c1..c3ed148 100644 --- a/poetry.lock +++ b/poetry.lock @@ -945,13 +945,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] From 8e51c9ed1d88adf44a14a2db069017cae69836ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 21:24:15 +0100 Subject: [PATCH 047/121] Bump certifi from 2024.2.2 to 2024.7.4 (#89) Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.2.2 to 2024.7.4. - [Commits](https://github.com/certifi/python-certifi/compare/2024.02.02...2024.07.04) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index c3ed148..7035863 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -62,13 +62,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] From c4d085c308a10d30caf78a1a73b0ab247ab3cdd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:17:35 +0100 Subject: [PATCH 048/121] Bump setuptools from 69.1.1 to 70.0.0 (#90) Bumps [setuptools](https://github.com/pypa/setuptools) from 69.1.1 to 70.0.0. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v69.1.1...v70.0.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7035863..91743dc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -882,19 +882,18 @@ files = [ [[package]] name = "setuptools" -version = "69.1.1" +version = "70.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, - {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, + {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, + {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "sseclient-py" From fd2094131e5e4972a17be5a8efa4cb379cc821ee Mon Sep 17 00:00:00 2001 From: Tushar <30565750+tushar5526@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:50:05 +0530 Subject: [PATCH 049/121] fix: Add a custom exception for invalid features (#86) * fix: Add a custom exception for invalid features * feat: add mock success response --- flagsmith/exceptions.py | 4 ++++ flagsmith/models.py | 6 ++++-- tests/test_flagsmith.py | 26 ++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/flagsmith/exceptions.py b/flagsmith/exceptions.py index c1c3b36..e499207 100644 --- a/flagsmith/exceptions.py +++ b/flagsmith/exceptions.py @@ -4,3 +4,7 @@ class FlagsmithClientError(Exception): class FlagsmithAPIError(FlagsmithClientError): pass + + +class FlagsmithFeatureDoesNotExistError(FlagsmithClientError): + pass diff --git a/flagsmith/models.py b/flagsmith/models.py index d06a95f..ac7d2cc 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -6,7 +6,7 @@ from flag_engine.features.models import FeatureStateModel from flagsmith.analytics import AnalyticsProcessor -from flagsmith.exceptions import FlagsmithClientError +from flagsmith.exceptions import FlagsmithFeatureDoesNotExistError @dataclass @@ -135,7 +135,9 @@ def get_flag(self, feature_name: str) -> typing.Union[DefaultFlag, Flag]: except KeyError: if self.default_flag_handler: return self.default_flag_handler(feature_name) - raise FlagsmithClientError("Feature does not exist: %s" % feature_name) + raise FlagsmithFeatureDoesNotExistError( + "Feature does not exist: %s" % feature_name + ) if self._analytics_processor and hasattr(flag, "feature_name"): self._analytics_processor.track_feature(flag.feature_name) diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 3ecc0fc..093aa88 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -11,15 +11,16 @@ from pytest_mock import MockerFixture from flagsmith import Flagsmith -from flagsmith.exceptions import FlagsmithAPIError +from flagsmith.exceptions import ( + FlagsmithAPIError, + FlagsmithFeatureDoesNotExistError, +) from flagsmith.models import DefaultFlag, Flags from flagsmith.offline_handlers import BaseOfflineHandler def test_flagsmith_starts_polling_manager_on_init_if_enabled( - mocker: MockerFixture, - server_api_key: str, - requests_session_response_ok: None, + mocker: MockerFixture, server_api_key: str, requests_session_response_ok: None ) -> None: # Given mock_polling_manager = mocker.MagicMock() @@ -559,3 +560,20 @@ def test_flagsmith_client_get_identity_flags__local_evaluation__returns_expected # Then assert flag.enabled is False assert flag.value == "some-overridden-value" + + +def test_custom_feature_error_raised_when_invalid_feature( + requests_session_response_ok: None, server_api_key: str +) -> None: + # Given + flagsmith = Flagsmith( + environment_key=server_api_key, + enable_local_evaluation=True, + ) + + flags = flagsmith.get_environment_flags() + + # Then + with pytest.raises(FlagsmithFeatureDoesNotExistError): + # When + flags.is_feature_enabled("non-existing-feature") From 297c382435b1f227db7205c515d7ae096c74d02f Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Wed, 17 Jul 2024 13:54:17 +0100 Subject: [PATCH 050/121] chore: Bump package, fix README (#92) --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c6dfc68..78b225d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [tool.poetry] name = "flagsmith" -version = "3.6.0" +version = "3.7.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" -readme = "Readme.md" +readme = "README.md" keywords = ["feature", "flag", "flagsmith", "remote", "config"] documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] From 0a11db5a1010c177856716e6b90292651fa5889b Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 19 Jul 2024 11:55:47 +0100 Subject: [PATCH 051/121] feat: Support transient identities and traits (#93) * feat: Support transient identities and traits - Support transient identities and traits - Bump requests - Bump mypy - Remove linting from CI in favour of pre-commit.ci --- .github/workflows/pytest.yml | 12 +- .pre-commit-config.yaml | 3 +- README.md | 4 +- flagsmith/flagsmith.py | 57 +++++---- flagsmith/streaming_manager.py | 2 +- flagsmith/types.py | 25 ++++ flagsmith/utils/identities.py | 39 ++++--- poetry.lock | 207 ++------------------------------- pyproject.toml | 14 +-- tests/test_flagsmith.py | 67 +++++++++++ 10 files changed, 166 insertions(+), 264 deletions(-) create mode 100644 flagsmith/types.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a70a392..f363b77 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,4 +1,4 @@ -name: Linting and Tests +name: Run Tests on: pull_request: @@ -9,7 +9,6 @@ on: jobs: test: runs-on: ubuntu-latest - name: Linting and Tests strategy: max-parallel: 4 @@ -33,14 +32,5 @@ jobs: pip install poetry poetry install --with dev - - name: Check Formatting - run: | - poetry run black --check . - poetry run flake8 . - poetry run isort --check . - - - name: Check Typing - run: poetry run mypy --strict . - - name: Run Tests run: poetry run pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d56e9ab..edd21f5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.10.1 hooks: - id: mypy args: [--strict] @@ -14,7 +14,6 @@ repos: rev: 24.3.0 hooks: - id: black - language_version: python3 - repo: https://github.com/pycqa/flake8 rev: 6.1.0 hooks: diff --git a/README.md b/README.md index 2df3a1b..16ff107 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ For full documentation visit ## Contributing -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code -of conduct, and the process for submitting pull requests +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull +requests ## Getting Help diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 0245b7a..8ab9205 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -19,23 +19,14 @@ from flagsmith.offline_handlers import BaseOfflineHandler from flagsmith.polling_manager import EnvironmentDataPollingManager from flagsmith.streaming_manager import EventStreamManager, StreamEvent -from flagsmith.utils.identities import Identity, generate_identities_data +from flagsmith.types import JsonType, TraitConfig, TraitMapping +from flagsmith.utils.identities import generate_identity_data logger = logging.getLogger(__name__) DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/" DEFAULT_REALTIME_API_URL = "https://realtime.flagsmith.com/" -JsonType = typing.Union[ - None, - int, - str, - bool, - typing.List["JsonType"], - typing.List[typing.Mapping[str, "JsonType"]], - typing.Dict[str, "JsonType"], -] - class Flagsmith: """A Flagsmith client. @@ -237,7 +228,9 @@ def get_environment_flags(self) -> Flags: def get_identity_flags( self, identifier: str, - traits: typing.Optional[typing.Mapping[str, TraitValue]] = None, + traits: typing.Optional[TraitMapping] = None, + *, + transient: bool = False, ) -> Flags: """ Get all the flags for the current environment for a given identity. Will also @@ -247,13 +240,20 @@ def get_identity_flags( :param identifier: a unique identifier for the identity in the current environment, e.g. email address, username, uuid :param traits: a dictionary of traits to add / update on the identity in - Flagsmith, e.g. {"num_orders": 10} + Flagsmith, e.g. `{"num_orders": 10}`. Envelope traits you don't want persisted + in a dictionary with `"transient"` and `"value"` keys, e.g. + `{"num_orders": 10, "color": {"value": "pink", "transient": True}}`. + :param transient: if `True`, the identity won't get persisted :return: Flags object holding all the flags for the given identity. """ traits = traits or {} if (self.offline_mode or self.enable_local_evaluation) and self._environment: return self._get_identity_flags_from_document(identifier, traits) - return self._get_identity_flags_from_api(identifier, traits) + return self._get_identity_flags_from_api( + identifier, + traits, + transient=transient, + ) def get_identity_segments( self, @@ -306,7 +306,7 @@ def _get_environment_flags_from_document(self) -> Flags: ) def _get_identity_flags_from_document( - self, identifier: str, traits: typing.Mapping[str, TraitValue] + self, identifier: str, traits: TraitMapping ) -> Flags: identity_model = self._get_identity_model(identifier, **traits) if self._environment is None: @@ -339,13 +339,23 @@ def _get_environment_flags_from_api(self) -> Flags: raise def _get_identity_flags_from_api( - self, identifier: str, traits: typing.Mapping[str, typing.Any] + self, + identifier: str, + traits: TraitMapping, + *, + transient: bool = False, ) -> Flags: + request_body = generate_identity_data( + identifier, + traits, + transient=transient, + ) try: - data = generate_identities_data(identifier, traits) json_response: typing.Dict[str, typing.List[typing.Dict[str, JsonType]]] = ( self._get_json_response( - url=self.identities_url, method="POST", body=data + url=self.identities_url, + method="POST", + body=request_body, ) ) return Flags.from_api_flags( @@ -364,9 +374,7 @@ def _get_json_response( self, url: str, method: str, - body: typing.Optional[ - typing.Union[Identity, typing.Dict[str, JsonType]] - ] = None, + body: typing.Optional[JsonType] = None, ) -> typing.Any: try: request_method = getattr(self.session, method.lower()) @@ -387,7 +395,7 @@ def _get_json_response( def _get_identity_model( self, identifier: str, - **traits: TraitValue, + **traits: typing.Union[TraitValue, TraitConfig], ) -> IdentityModel: if not self._environment: raise FlagsmithClientError( @@ -395,7 +403,10 @@ def _get_identity_model( ) trait_models = [ - TraitModel(trait_key=key, trait_value=value) + TraitModel( + trait_key=key, + trait_value=value["value"] if isinstance(value, dict) else value, + ) for key, value in traits.items() ] diff --git a/flagsmith/streaming_manager.py b/flagsmith/streaming_manager.py index 3fc6817..4afe87a 100644 --- a/flagsmith/streaming_manager.py +++ b/flagsmith/streaming_manager.py @@ -22,7 +22,7 @@ def __init__( stream_url: str, on_event: Callable[[StreamEvent], None], request_timeout_seconds: Optional[int] = None, - **kwargs: typing.Any + **kwargs: typing.Any, ) -> None: super().__init__(*args, **kwargs) self._stop_event = threading.Event() diff --git a/flagsmith/types.py b/flagsmith/types.py new file mode 100644 index 0000000..b2a41a3 --- /dev/null +++ b/flagsmith/types.py @@ -0,0 +1,25 @@ +import typing + +from flag_engine.identities.traits.types import TraitValue +from typing_extensions import TypeAlias + +_JsonScalarType: TypeAlias = typing.Union[ + int, + str, + float, + bool, + None, +] +JsonType: TypeAlias = typing.Union[ + _JsonScalarType, + typing.Dict[str, "JsonType"], + typing.List["JsonType"], +] + + +class TraitConfig(typing.TypedDict): + value: TraitValue + transient: bool + + +TraitMapping: TypeAlias = typing.Mapping[str, typing.Union[TraitValue, TraitConfig]] diff --git a/flagsmith/utils/identities.py b/flagsmith/utils/identities.py index 082721d..a1a419e 100644 --- a/flagsmith/utils/identities.py +++ b/flagsmith/utils/identities.py @@ -1,21 +1,26 @@ import typing -from flag_engine.identities.traits.types import TraitValue +from flagsmith.types import JsonType, TraitMapping -Identity = typing.TypedDict( - "Identity", - {"identifier": str, "traits": typing.List[typing.Mapping[str, TraitValue]]}, -) - -def generate_identities_data( - identifier: str, traits: typing.Optional[typing.Mapping[str, TraitValue]] = None -) -> Identity: - return { - "identifier": identifier, - "traits": ( - [{"trait_key": k, "trait_value": v} for k, v in traits.items()] - if traits - else [] - ), - } +def generate_identity_data( + identifier: str, + traits: TraitMapping, + *, + transient: bool, +) -> JsonType: + identity_data: typing.Dict[str, JsonType] = {"identifier": identifier} + traits_data: typing.List[JsonType] = [] + for trait_key, trait_value in traits.items(): + trait_data: typing.Dict[str, JsonType] = {"trait_key": trait_key} + if isinstance(trait_value, dict): + trait_data["trait_value"] = trait_value["value"] + if trait_value.get("transient"): + trait_data["transient"] = True + else: + trait_data["trait_value"] = trait_value + traits_data.append(trait_data) + identity_data["traits"] = traits_data + if transient: + identity_data["transient"] = True + return identity_data diff --git a/poetry.lock b/poetry.lock index 91743dc..4c0bf5e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,52 +14,6 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} -[[package]] -name = "black" -version = "24.3.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, - {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, - {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, - {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, - {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, - {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, - {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, - {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, - {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, - {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, - {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, - {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, - {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, - {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, - {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, - {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, - {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, - {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, - {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, - {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, - {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, - {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2024.7.4" @@ -181,20 +135,6 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.6" @@ -329,22 +269,6 @@ pydantic = ">=2.3.0,<3" pydantic-collections = ">=0.5.1,<1" semver = ">=3.0.1" -[[package]] -name = "flake8" -version = "6.1.0" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.8.1" -files = [ - {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, - {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" -pyflakes = ">=3.1.0,<3.2.0" - [[package]] name = "identify" version = "2.5.35" @@ -381,89 +305,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy" -version = "1.9.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "nodeenv" version = "1.8.0" @@ -489,17 +330,6 @@ files = [ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - [[package]] name = "platformdirs" version = "4.2.0" @@ -548,17 +378,6 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "pycodestyle" -version = "2.11.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, -] - [[package]] name = "pydantic" version = "2.6.3" @@ -684,17 +503,6 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" -[[package]] -name = "pyflakes" -version = "3.1.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, - {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, -] - [[package]] name = "pytest" version = "7.4.4" @@ -777,7 +585,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -814,13 +621,13 @@ files = [ [[package]] name = "requests" -version = "2.32.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ - {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, - {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -919,13 +726,13 @@ files = [ [[package]] name = "types-requests" -version = "2.31.0.20240218" +version = "2.32.0.20240712" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.20240218.tar.gz", hash = "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"}, - {file = "types_requests-2.31.0.20240218-py3-none-any.whl", hash = "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b"}, + {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, + {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, ] [package.dependencies] @@ -982,4 +789,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "555fbec9cbe8ecb76b900ee4958ed71b134f15d4c22d4c9db1c73a9c459306a8" +content-hash = "0e303bca656a71e8f3ca841709c427478812997c7adff1a61230313162842348" diff --git a/pyproject.toml b/pyproject.toml index 78b225d..7c145fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,8 @@ packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] python = ">=3.8.1,<4" -requests = "^2.27.1" -requests-futures = "^1.0.0" +requests = "^2.32.3" +requests-futures = "^1.0.1" flagsmith-flag-engine = "^5.1.0" sseclient-py = "^1.8.0" @@ -21,20 +21,18 @@ optional = true [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" +pytest-cov = "^4.1.0" pytest-mock = "^3.6.1" -black = ">=23.3,<25.0" pre-commit = "^2.17.0" responses = "^0.24.1" -flake8 = "^6.1.0" -isort = "^5.12.0" -mypy = "^1.7.1" -types-requests = "^2.31.0.10" -pytest-cov = "^4.1.0" +types-requests = "^2.32" [tool.mypy] plugins = ["pydantic.mypy"] exclude = ["example/*"] +[tool.black] +target-version = ["py38"] [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 093aa88..3dde406 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -9,6 +9,7 @@ from flag_engine.environments.models import EnvironmentModel from flag_engine.features.models import FeatureModel, FeatureStateModel from pytest_mock import MockerFixture +from responses import matchers from flagsmith import Flagsmith from flagsmith.exceptions import ( @@ -172,6 +173,72 @@ def test_get_identity_flags_uses_local_environment_when_available( assert identity_flags[0].value == feature_state.get_value() +@responses.activate() +def test_get_identity_flags__transient_identity__calls_expected( + flagsmith: Flagsmith, + identities_json: str, +) -> None: + # Given + responses.add( + method="POST", + url=flagsmith.identities_url, + body=identities_json, + match=[ + matchers.json_params_matcher( + { + "identifier": "identifier", + "traits": [ + {"trait_key": "some_trait", "trait_value": "some_value"} + ], + "transient": True, + } + ) + ], + ) + + # When & Then + flagsmith.get_identity_flags( + "identifier", + traits={"some_trait": "some_value"}, + transient=True, + ) + + +@responses.activate() +def test_get_identity_flags__transient_trait_keys__calls_expected( + flagsmith: Flagsmith, + identities_json: str, + environment_model: EnvironmentModel, + mocker: MockerFixture, +) -> None: + # Given + responses.add( + method="POST", + url=flagsmith.identities_url, + body=identities_json, + match=[ + matchers.json_params_matcher( + { + "identifier": "identifier", + "traits": [ + { + "trait_key": "some_trait", + "trait_value": "some_value", + "transient": True, + } + ], + }, + ) + ], + ) + + # When & Then + flagsmith.get_identity_flags( + "identifier", + traits={"some_trait": {"value": "some_value", "transient": True}}, + ) + + def test_request_connection_error_raises_flagsmith_api_error( mocker: MockerFixture, api_key: str ) -> None: From a37a891a4707ccf707bc8bd8e0112bb88e01b203 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 23 Jul 2024 16:12:51 +0100 Subject: [PATCH 052/121] chore: Add release-please (#94) * chore: Add release-please * add py3.12 --- .github/workflows/publish.yml | 2 +- .github/workflows/pull-request.yml | 42 ++++ .github/workflows/pytest.yml | 6 +- .github/workflows/release-please.yml | 19 ++ .release-please-manifest.json | 3 + CHANGELOG.md | 276 +++++++++++++++++++++++++++ release-please-config.json | 62 ++++++ 7 files changed, 406 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/pull-request.yml create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 CHANGELOG.md create mode 100644 release-please-config.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 898081d..d793046 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Publish Pypi Package +name: Publish PyPI Package on: push: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..9938d50 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,42 @@ +name: Pull Request + +on: + pull_request: + +jobs: + conventional-commit: + name: Conventional Commit + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Check PR Conventional Commit title + uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | # mirrors changelog-sections in the /release-please-config.json + feat + fix + ci + docs + deps + refactor + test + chore + - name: Auto-label PR with Conventional Commit title + uses: kramen22/conventional-release-labels@v1 + with: + type_labels: | + { + "feat": "feature", + "fix": "fix", + "ci": "ci-cd", + "docs": "docs", + "deps": "dependencies", + "perf": "performance", + "refactor": "refactor", + "test": "testing", + "chore": "chore" + } + ignored_types: '[]' diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f363b77..743f3a7 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,4 +1,4 @@ -name: Run Tests +name: Tests on: pull_request: @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest strategy: - max-parallel: 4 + max-parallel: 5 matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - name: Cloning repo diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..187026e --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,19 @@ +name: Update release PR + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.RELEASE_PLEASE_GITHUB_TOKEN }} + release-type: simple diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..2e30867 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "3.7.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..695a463 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,276 @@ +# Changelog + + + +## [v3.7.0](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.7.0) - 17 Jul 2024 + +### What's Changed + +- Bump black from 23.12.1 to 24.3.0 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/81 +- chore: update github actions by [@dabeeeenster](https://github.com/dabeeeenster) in + https://github.com/Flagsmith/flagsmith-python-client/pull/82 +- Remove pytz and replace usage with core python modules by [@MerijnBol](https://github.com/MerijnBol) in + https://github.com/Flagsmith/flagsmith-python-client/pull/80 +- docs: misc README improvements by [@rolodato](https://github.com/rolodato) in + https://github.com/Flagsmith/flagsmith-python-client/pull/84 +- Bump idna from 3.6 to 3.7 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/83 +- Bump requests from 2.31.0 to 2.32.0 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/85 +- Bump urllib3 from 2.2.1 to 2.2.2 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/87 +- Bump certifi from 2024.2.2 to 2024.7.4 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/89 +- Bump setuptools from 69.1.1 to 70.0.0 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/90 +- fix: Add a custom exception for invalid features by [@tushar5526](https://github.com/tushar5526) in + https://github.com/Flagsmith/flagsmith-python-client/pull/86 +- chore: Bump package, fix README by [@khvn26](https://github.com/khvn26) in + https://github.com/Flagsmith/flagsmith-python-client/pull/92 + +### New Contributors + +- [@MerijnBol](https://github.com/MerijnBol) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/80 +- [@rolodato](https://github.com/rolodato) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/84 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.6.0...v3.7.0 + +[Changes][v3.7.0] + + + +## [v3.6.0](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.6.0) - 14 Mar 2024 + +### What's Changed + +- [##61](https://github.com/Flagsmith/flagsmith-python-client/issues/61) SSE streaming manager by + [@bne](https://github.com/bne) in https://github.com/Flagsmith/flagsmith-python-client/pull/73 +- chore: remove examples by [@dabeeeenster](https://github.com/dabeeeenster) in + https://github.com/Flagsmith/flagsmith-python-client/pull/75 +- feat: Add identity overrides to local evaluation mode by [@khvn26](https://github.com/khvn26) in + https://github.com/Flagsmith/flagsmith-python-client/pull/72 +- feat: strict typing by [@tushar5526](https://github.com/tushar5526) in + https://github.com/Flagsmith/flagsmith-python-client/pull/70 and + https://github.com/Flagsmith/flagsmith-python-client/pull/71 +- ci: run pytest on push to main by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/78 +- fix: Set the environment for local evaluation mode on init by [@zachaysan](https://github.com/zachaysan) in + https://github.com/Flagsmith/flagsmith-python-client/pull/76 +- chore: version bump to 3.6.0 by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/79 + +### New Contributors + +- [@tushar5526](https://github.com/tushar5526) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/71 +- [@bne](https://github.com/bne) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/73 +- [@zachaysan](https://github.com/zachaysan) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/76 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.5.0...v3.6.0 + +[Changes][v3.6.0] + + + +## [Version 3.5.0 (v3.5.0)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.5.0) - 23 Nov 2023 + +### Compatibility Notes + +Flagsmith Python SDK 3.5.0 brings the new version of Flagsmith engine that depends on Pydantic V2. If you're still using +Pydantic V1 in your project, consider doing one of the following: + +- Change your `pydantic` imports to `pydantic.v1`. +- Use the [bump-pydantic](https://github.com/pydantic/bump-pydantic) tool to migrate your code semi-automatically. + +Refer to the [Pydantic V2 migration guide](https://docs.pydantic.dev/latest/migration/) for more info. + +### What's Changed + +- Bump `flagsmith-flag-engine` to 5.0.0 by [@khvn26](https://github.com/khvn26) in + https://github.com/Flagsmith/flagsmith-python-client/pull/69 +- Ensure polling thread is resilient to errors and exceptions by [@goncalossilva](https://github.com/goncalossilva) in + https://github.com/Flagsmith/flagsmith-python-client/pull/60 +- Bump certifi from 2023.5.7 to 2023.7.22 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/56 +- Bump urllib3 from 2.0.4 to 2.0.7 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/64 + +### New Contributors + +- [@goncalossilva](https://github.com/goncalossilva) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/60 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.4.0...v3.5.0 + +[Changes][v3.5.0] + + + +## [Version 3.4.0 (v3.4.0)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.4.0) - 31 Jul 2023 + +### What's Changed + +- Implementation of offline mode (single client class) by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/50 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.3.0...v3.4.0 + +[Changes][v3.4.0] + + + +## [Version 3.3.0 (v3.3.0)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.3.0) - 27 Jul 2023 + +### What's Changed + +- Update flag-engine by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/49 + +### New Contributors + +- [@khvn26](https://github.com/khvn26) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/52 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.2.2...v3.3.0 + +[Changes][v3.3.0] + + + +## [Version 3.2.2 (v3.2.2)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.2.2) - 07 Jul 2023 + +### What's Changed + +- Use daemon argument to ensure that polling manager is killed by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/47 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.2.1...v3.2.2 + +[Changes][v3.2.2] + + + +## [Version 3.2.1 (v3.2.1)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.2.1) - 19 May 2023 + +### What's Changed + +- Bump flask from 2.0.2 to 2.2.5 in /example by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/44 +- Bump certifi from 2021.10.8 to 2022.12.7 by [@dependabot](https://github.com/dependabot) in + https://github.com/Flagsmith/flagsmith-python-client/pull/36 +- improvement/general-housekeeping by [@dabeeeenster](https://github.com/dabeeeenster) in + https://github.com/Flagsmith/flagsmith-python-client/pull/43 +- chore/bump-version by [@dabeeeenster](https://github.com/dabeeeenster) in + https://github.com/Flagsmith/flagsmith-python-client/pull/45 + +### New Contributors + +- [@dependabot](https://github.com/dependabot) made their first contribution in + https://github.com/Flagsmith/flagsmith-python-client/pull/44 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.2.0...v3.2.1 + +[Changes][v3.2.1] + + + +## [Version 3.2.0 (v3.2.0)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.2.0) - 13 Jan 2023 + +### What's Changed + +- Add proxies option to Flagsmith by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/39 +- Release 3.2.0 by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/38 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.1.0...v3.2.0 + +[Changes][v3.2.0] + + + +## [Version 3.1.0 (v3.1.0)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.1.0) - 01 Nov 2022 + +### What's Changed + +- Upgrade engine (2.3.0) by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/34 +- Release 3.1.0 by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/33 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.0.1...v3.1.0 + +[Changes][v3.1.0] + + + +## [Version 3.0.1 (v3.0.1)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.0.1) - 13 Jul 2022 + +### What's Changed + +- Use feature name instead of feature id by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/29 +- Release 3.0.1 by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/30 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.0.0...v3.0.1 + +[Changes][v3.0.1] + + + +## [Version 3.0.0 (v3.0.0)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.0.0) - 07 Jun 2022 + +### What's Changed + +- Feature/rewrite for client side eval by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/17 +- Refactor default flag logic by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/22 +- Expose segments by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/24 +- Prevent initialisation with local evaluation without server key by [@matthewelwell](https://github.com/matthewelwell) + in https://github.com/Flagsmith/flagsmith-python-client/pull/25 +- Update default url to point to edge by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/27 +- Release 3.0.0 by [@matthewelwell](https://github.com/matthewelwell) in + https://github.com/Flagsmith/flagsmith-python-client/pull/16 + +**Full Changelog**: https://github.com/Flagsmith/flagsmith-python-client/compare/v1.0.1...v3.0.0 + +[Changes][v3.0.0] + + + +## [Version 3.0.0 alpha 2 (v3.0.0-alpha.2)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.0.0-alpha.2) - 30 May 2022 + +[Changes][v3.0.0-alpha.2] + + + +## [Version 3.0.0 - Alpha 1 (v3.0.0-alpha.1)](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.0.0-alpha.1) - 17 May 2022 + +First alpha release of v3.0.0 + +[Changes][v3.0.0-alpha.1] + +[v3.7.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.6.0...v3.7.0 +[v3.6.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.5.0...v3.6.0 +[v3.5.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.4.0...v3.5.0 +[v3.4.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.3.0...v3.4.0 +[v3.3.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.2.2...v3.3.0 +[v3.2.2]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.2.1...v3.2.2 +[v3.2.1]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.2.0...v3.2.1 +[v3.2.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.1.0...v3.2.0 +[v3.1.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.0.1...v3.1.0 +[v3.0.1]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.0.0...v3.0.1 +[v3.0.0]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.0.0-alpha.2...v3.0.0 +[v3.0.0-alpha.2]: https://github.com/Flagsmith/flagsmith-python-client/compare/v3.0.0-alpha.1...v3.0.0-alpha.2 +[v3.0.0-alpha.1]: https://github.com/Flagsmith/flagsmith-python-client/tree/v3.0.0-alpha.1 + + diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..0a099a3 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,62 @@ +{ + "bootstrap-sha": "fd2094131e5e4972a17be5a8efa4cb379cc821ee", + "packages": { + ".": { + "release-type": "simple", + "changelog-path": "CHANGELOG.md", + "bump-minor-pre-major": false, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false, + "include-component-in-tag": false + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "changelog-sections": [ + { + "type": "feat", + "hidden": false, + "section": "Features" + }, + { + "type": "fix", + "hidden": false, + "section": "Bug Fixes" + }, + { + "type": "ci", + "hidden": true, + "section": "CI" + }, + { + "type": "docs", + "hidden": true, + "section": "Docs" + }, + { + "type": "deps", + "hidden": true, + "section": "Dependency Updates" + }, + { + "type": "perf", + "hidden": true, + "section": "Performance Improvements" + }, + { + "type": "refactor", + "hidden": true, + "section": "Refactoring" + }, + { + "type": "test", + "hidden": true, + "section": "Tests" + }, + { + "type": "chore", + "hidden": true, + "section": "Other" + } + ] +} From 92e19f4e0f152bae063dbeca8e7907fee6ec4913 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:27:18 +0100 Subject: [PATCH 053/121] ci: pre-commit autoupdate (#95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0) - [github.com/PyCQA/isort: 5.12.0 → 5.13.2](https://github.com/PyCQA/isort/compare/5.12.0...5.13.2) - [github.com/psf/black: 24.3.0 → 24.4.2](https://github.com/psf/black/compare/24.3.0...24.4.2) - [github.com/pycqa/flake8: 6.1.0 → 7.1.0](https://github.com/pycqa/flake8/compare/6.1.0...7.1.0) - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.6.0) - [github.com/pre-commit/mirrors-prettier: v2.7.1 → v4.0.0-alpha.8](https://github.com/pre-commit/mirrors-prettier/compare/v2.7.1...v4.0.0-alpha.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index edd21f5..e434989 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,29 +1,29 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.1 + rev: v1.11.0 hooks: - id: mypy args: [--strict] additional_dependencies: [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/psf/black - rev: 24.3.0 + rev: 24.4.2 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 + rev: 7.1.0 hooks: - id: flake8 name: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-yaml - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + rev: v4.0.0-alpha.8 hooks: - id: prettier From b179c83b13d039507b09bca3f502b8199fc31934 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:32:35 +0100 Subject: [PATCH 054/121] deps: [pre-commit.ci] pre-commit autoupdate (#97) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1) - [github.com/psf/black: 24.4.2 → 24.8.0](https://github.com/psf/black/compare/24.4.2...24.8.0) - [github.com/pycqa/flake8: 7.1.0 → 7.1.1](https://github.com/pycqa/flake8/compare/7.1.0...7.1.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e434989..8e30251 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.0 + rev: v1.11.1 hooks: - id: mypy args: [--strict] @@ -11,11 +11,11 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 7.1.0 + rev: 7.1.1 hooks: - id: flake8 name: flake8 From 6f6d5950bc3a6befd953dc1a24ef497a4a018c7b Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 12 Aug 2024 12:21:23 +0100 Subject: [PATCH 055/121] fix: Offline handler not used as fallback for local evaluation mode during init (#100) --- flagsmith/flagsmith.py | 12 ++++++----- flagsmith/polling_manager.py | 9 +------- flagsmith/streaming_manager.py | 8 ------- tests/test_flagsmith.py | 38 +++++++++++++++++++++++++++++++++ tests/test_streaming_manager.py | 30 -------------------------- 5 files changed, 46 insertions(+), 51 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 8ab9205..9f62958 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -159,8 +159,10 @@ def __init__( ) def _initialise_local_evaluation(self) -> None: + # To ensure that the environment is set before allowing subsequent + # method calls, update the environment manually. + self.update_environment() if self.enable_realtime_updates: - self.update_environment() if not self._environment: raise ValueError("Unable to get environment from API key") @@ -175,9 +177,6 @@ def _initialise_local_evaluation(self) -> None: self.event_stream_thread.start() else: - # To ensure that the environment is set before allowing subsequent - # method calls, update the environment manually. - self.update_environment() self.environment_data_polling_manager_thread = ( EnvironmentDataPollingManager( main=self, @@ -281,7 +280,10 @@ def get_identity_segments( return [Segment(id=sm.id, name=sm.name) for sm in segment_models] def update_environment(self) -> None: - self._environment = self._get_environment_from_api() + try: + self._environment = self._get_environment_from_api() + except (FlagsmithAPIError, requests.RequestException): + logger.exception("Error updating environment") self._update_overrides() def _update_overrides(self) -> None: diff --git a/flagsmith/polling_manager.py b/flagsmith/polling_manager.py index 85b8486..b152e68 100644 --- a/flagsmith/polling_manager.py +++ b/flagsmith/polling_manager.py @@ -5,10 +5,6 @@ import time import typing -import requests - -from flagsmith.exceptions import FlagsmithAPIError - if typing.TYPE_CHECKING: from flagsmith import Flagsmith @@ -30,10 +26,7 @@ def __init__( def run(self) -> None: while not self._stop_event.is_set(): - try: - self.main.update_environment() - except (FlagsmithAPIError, requests.RequestException): - logger.exception("Failed to update environment") + self.main.update_environment() time.sleep(self.refresh_interval_seconds) def stop(self) -> None: diff --git a/flagsmith/streaming_manager.py b/flagsmith/streaming_manager.py index 4afe87a..95eca20 100644 --- a/flagsmith/streaming_manager.py +++ b/flagsmith/streaming_manager.py @@ -1,4 +1,3 @@ -import logging import threading import typing from typing import Callable, Generator, Optional, Protocol, cast @@ -6,10 +5,6 @@ import requests import sseclient -from flagsmith.exceptions import FlagsmithAPIError - -logger = logging.getLogger(__name__) - class StreamEvent(Protocol): data: str @@ -48,9 +43,6 @@ def run(self) -> None: except requests.exceptions.ReadTimeout: pass - except (FlagsmithAPIError, requests.RequestException): - logger.exception("Error handling event stream") - def stop(self) -> None: self._stop_event.set() diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 3dde406..2a37815 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -526,6 +526,44 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( assert identity_flags.get_feature_value("some_feature") == "some-value" +@responses.activate() +def test_offline_mode__local_evaluation__correct_fallback( + mocker: MockerFixture, + environment_model: EnvironmentModel, + caplog: pytest.LogCaptureFixture, +) -> None: + # Given + api_url = "http://some.flagsmith.com/api/v1/" + mock_offline_handler = mocker.MagicMock(spec=BaseOfflineHandler) + mock_offline_handler.get_environment.return_value = environment_model + + flagsmith = Flagsmith( + environment_key="ser.some-key", + api_url=api_url, + enable_local_evaluation=True, + offline_handler=mock_offline_handler, + ) + + responses.get(flagsmith.environment_url, status=500) + + # When + environment_flags = flagsmith.get_environment_flags() + identity_flags = flagsmith.get_identity_flags("identity", traits={}) + + # Then + mock_offline_handler.get_environment.assert_called_once_with() + + assert environment_flags.is_feature_enabled("some_feature") is True + assert environment_flags.get_feature_value("some_feature") == "some-value" + + assert identity_flags.is_feature_enabled("some_feature") is True + assert identity_flags.get_feature_value("some_feature") == "some-value" + + [error_log_record] = caplog.records + assert error_log_record.levelname == "ERROR" + assert error_log_record.message == "Error updating environment" + + def test_cannot_use_offline_mode_without_offline_handler() -> None: with pytest.raises(ValueError) as e: # When diff --git a/tests/test_streaming_manager.py b/tests/test_streaming_manager.py index bd4bf93..7d33283 100644 --- a/tests/test_streaming_manager.py +++ b/tests/test_streaming_manager.py @@ -36,36 +36,6 @@ def test_stream_manager_handles_timeout( streaming_manager.stop() -def test_stream_manager_handles_request_exception( - mocked_responses: responses.RequestsMock, - caplog: pytest.LogCaptureFixture, -) -> None: - stream_url = ( - "https://realtime.flagsmith.com/sse/environments/B62qaMZNwfiqT76p38ggrQ/stream" - ) - - mocked_responses.get(stream_url, body=requests.RequestException()) - mocked_responses.get(stream_url, body=FlagsmithAPIError()) - - streaming_manager = EventStreamManager( - stream_url=stream_url, - on_event=MagicMock(), - daemon=True, - ) - - streaming_manager.start() - - time.sleep(0.01) - - assert streaming_manager.is_alive() - - streaming_manager.stop() - - for record in caplog.records: - assert record.levelname == "ERROR" - assert record.message == "Error handling event stream" - - def test_environment_updates_on_recent_event( server_api_key: str, mocker: MockerFixture ) -> None: From f7567e82dd14e5785b6417e305f8c8b8d5a15e27 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 12 Aug 2024 16:36:57 +0100 Subject: [PATCH 056/121] refactor: Improve `EventStreamManager` (#101) --- flagsmith/flagsmith.py | 45 +-- flagsmith/streaming_manager.py | 20 +- poetry.lock | 525 +++++++++++++++++--------------- pyproject.toml | 1 + tests/test_flagsmith.py | 6 +- tests/test_streaming_manager.py | 63 +--- 6 files changed, 304 insertions(+), 356 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 9f62958..f5d6882 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -1,8 +1,8 @@ -import json import logging import typing -from datetime import datetime, timezone +from datetime import timezone +import pydantic import requests from flag_engine import engine from flag_engine.environments.models import EnvironmentModel @@ -188,21 +188,6 @@ def _initialise_local_evaluation(self) -> None: self.environment_data_polling_manager_thread.start() def handle_stream_event(self, event: StreamEvent) -> None: - try: - event_data = json.loads(event.data) - except json.JSONDecodeError as e: - raise FlagsmithAPIError("Unable to get valid json from event data.") from e - - try: - stream_updated_at = datetime.fromtimestamp(event_data.get("updated_at")) - except TypeError as e: - raise FlagsmithAPIError( - "Unable to get valid timestamp from event data." - ) from e - - if stream_updated_at.tzinfo is None: - stream_updated_at = stream_updated_at.astimezone(timezone.utc) - if not self._environment: raise ValueError( "Unable to access environment. Environment should not be null" @@ -211,7 +196,7 @@ def handle_stream_event(self, event: StreamEvent) -> None: if environment_updated_at.tzinfo is None: environment_updated_at = environment_updated_at.astimezone(timezone.utc) - if stream_updated_at > environment_updated_at: + if event.updated_at > environment_updated_at: self.update_environment() def get_environment_flags(self) -> Flags: @@ -282,17 +267,13 @@ def get_identity_segments( def update_environment(self) -> None: try: self._environment = self._get_environment_from_api() - except (FlagsmithAPIError, requests.RequestException): + except (FlagsmithAPIError, pydantic.ValidationError): logger.exception("Error updating environment") - self._update_overrides() - - def _update_overrides(self) -> None: - if not self._environment: - return - if overrides := self._environment.identity_overrides: - self._identity_overrides_by_identifier = { - identity.identifier: identity for identity in overrides - } + else: + if overrides := self._environment.identity_overrides: + self._identity_overrides_by_identifier = { + identity.identifier: identity for identity in overrides + } def _get_environment_from_api(self) -> EnvironmentModel: environment_data = self._get_json_response(self.environment_url, method="GET") @@ -383,13 +364,9 @@ def _get_json_response( response = request_method( url, json=body, timeout=self.request_timeout_seconds ) - if response.status_code != 200: - raise FlagsmithAPIError( - "Invalid request made to Flagsmith API. Response status code: %d", - response.status_code, - ) + response.raise_for_status() return response.json() - except (requests.ConnectionError, json.JSONDecodeError) as e: + except requests.RequestException as e: raise FlagsmithAPIError( "Unable to get valid response from Flagsmith API." ) from e diff --git a/flagsmith/streaming_manager.py b/flagsmith/streaming_manager.py index 95eca20..d299fe8 100644 --- a/flagsmith/streaming_manager.py +++ b/flagsmith/streaming_manager.py @@ -1,13 +1,17 @@ +import logging import threading import typing -from typing import Callable, Generator, Optional, Protocol, cast +from typing import Callable, Optional +import pydantic import requests import sseclient +logger = logging.getLogger(__name__) -class StreamEvent(Protocol): - data: str + +class StreamEvent(pydantic.BaseModel): + updated_at: pydantic.AwareDatetime class EventStreamManager(threading.Thread): @@ -34,14 +38,12 @@ def run(self) -> None: headers={"Accept": "application/json, text/event-stream"}, timeout=self.request_timeout_seconds, ) as response: - sse_client = sseclient.SSEClient( - cast(Generator[bytes, None, None], response) - ) + sse_client = sseclient.SSEClient(chunk for chunk in response) for event in sse_client.events(): - self.on_event(event) + self.on_event(StreamEvent.model_validate_json(event.data)) - except requests.exceptions.ReadTimeout: - pass + except (requests.RequestException, pydantic.ValidationError): + logger.exception("Error opening or reading from the event stream") def stop(self) -> None: self._stop_event.set() diff --git a/poetry.lock b/poetry.lock index 4c0bf5e..d317563 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [package.dependencies] @@ -148,63 +148,83 @@ files = [ [[package]] name = "coverage" -version = "7.4.3" +version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, - {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, - {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, - {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, - {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, - {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, - {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, - {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, - {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, - {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, - {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, - {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, - {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, - {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "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"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "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"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "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"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "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"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "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"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "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"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "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"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, ] [package.dependencies] @@ -226,13 +246,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -240,18 +260,18 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.13.1" +version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -271,13 +291,13 @@ semver = ">=3.0.1" [[package]] name = "identify" -version = "2.5.35" +version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, ] [package.extras] @@ -307,53 +327,51 @@ files = [ [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.1" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -380,32 +398,35 @@ virtualenv = ">=20.10.0" [[package]] name = "pydantic" -version = "2.6.3" +version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.3-py3-none-any.whl", hash = "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a"}, - {file = "pydantic-2.6.3.tar.gz", hash = "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.3" -typing-extensions = ">=4.6.1" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-collections" -version = "0.5.4" +version = "0.6.0" description = "Collections of pydantic models" optional = false python-versions = "*" files = [ - {file = "pydantic-collections-0.5.4.tar.gz", hash = "sha256:5bce65519456b4829f918c2456d58aac3620a866603461a702aafffe08845966"}, - {file = "pydantic_collections-0.5.4-py3-none-any.whl", hash = "sha256:5d107170c89fb17de229f5e8c4b4355af27594444fd0f93086048ccafa69238b"}, + {file = "pydantic_collections-0.6.0-py3-none-any.whl", hash = "sha256:ec559722abf6a0f80e6f00b3d28f0f39c0ed5feb1641166230eb75e9da880162"}, + {file = "pydantic_collections-0.6.0.tar.gz", hash = "sha256:c34d3fd1df5600b315cdecdd8e74eacd4c8c607b7e3f2c9392b2a15850a4ef9e"}, ] [package.dependencies] @@ -414,90 +435,100 @@ typing-extensions = ">=4.7.1" [[package]] name = "pydantic-core" -version = "2.16.3" -description = "" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, - {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, - {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, - {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, - {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, - {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, - {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, - {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, - {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, - {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, - {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, - {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, - {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, - {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, ] [package.dependencies] @@ -545,78 +576,81 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-mock" -version = "3.12.0" +version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, - {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, ] [package.dependencies] -pytest = ">=5.0" +pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pyyaml" -version = "6.0.1" +version = "6.0.2" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] @@ -687,21 +721,6 @@ files = [ {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, ] -[[package]] -name = "setuptools" -version = "70.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "sseclient-py" version = "1.8.0" @@ -740,13 +759,13 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -768,13 +787,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, ] [package.dependencies] @@ -783,10 +802,10 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "0e303bca656a71e8f3ca841709c427478812997c7adff1a61230313162842348" +content-hash = "271e7bd94df6cfdf332704f14a7fe4fab36d58ba7e2d423f0cc242c5b53d91fc" diff --git a/pyproject.toml b/pyproject.toml index 7c145fa..2dcdeb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ requests = "^2.32.3" requests-futures = "^1.0.1" flagsmith-flag-engine = "^5.1.0" sseclient-py = "^1.8.0" +pydantic = "^2" [tool.poetry.group.dev] optional = true diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 2a37815..a76b6fe 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -537,6 +537,8 @@ def test_offline_mode__local_evaluation__correct_fallback( mock_offline_handler = mocker.MagicMock(spec=BaseOfflineHandler) mock_offline_handler.get_environment.return_value = environment_model + responses.get(api_url + "environment-document/", status=500) + flagsmith = Flagsmith( environment_key="ser.some-key", api_url=api_url, @@ -544,7 +546,9 @@ def test_offline_mode__local_evaluation__correct_fallback( offline_handler=mock_offline_handler, ) - responses.get(flagsmith.environment_url, status=500) + # We only verify local evaluation mode init phase here. + # Prevent additional API calls. + flagsmith.environment_data_polling_manager_thread.stop() # When environment_flags = flagsmith.get_environment_flags() diff --git a/tests/test_streaming_manager.py b/tests/test_streaming_manager.py index 7d33283..001c964 100644 --- a/tests/test_streaming_manager.py +++ b/tests/test_streaming_manager.py @@ -2,14 +2,12 @@ from datetime import datetime from unittest.mock import MagicMock, Mock -import pytest import requests import responses from pytest_mock import MockerFixture from flagsmith import Flagsmith -from flagsmith.exceptions import FlagsmithAPIError -from flagsmith.streaming_manager import EventStreamManager +from flagsmith.streaming_manager import EventStreamManager, StreamEvent def test_stream_manager_handles_timeout( @@ -48,9 +46,7 @@ def test_environment_updates_on_recent_event( flagsmith._environment = MagicMock() flagsmith._environment.updated_at = environment_updated_at flagsmith.handle_stream_event( - event=Mock( - data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', - ) + event=StreamEvent(updated_at=stream_updated_at.timestamp()) ) assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_called_once() @@ -69,9 +65,7 @@ def test_environment_does_not_update_on_past_event( flagsmith._environment.updated_at = environment_updated_at flagsmith.handle_stream_event( - event=Mock( - data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', - ) + event=StreamEvent(updated_at=stream_updated_at.timestamp()) ) assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_not_called() @@ -90,56 +84,7 @@ def test_environment_does_not_update_on_same_event( flagsmith._environment.updated_at = environment_updated_at flagsmith.handle_stream_event( - event=Mock( - data=f'{{"updated_at": {stream_updated_at.timestamp()}}}\n\n', - ) + event=StreamEvent(updated_at=stream_updated_at.timestamp()) ) assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_not_called() - - -def test_invalid_json_payload(server_api_key: str, mocker: MockerFixture) -> None: - mocker.patch("flagsmith.Flagsmith.update_environment") - flagsmith = Flagsmith(environment_key=server_api_key) - - with pytest.raises(FlagsmithAPIError): - flagsmith.handle_stream_event( - event=Mock( - data='{"updated_at": test}\n\n', - ) - ) - - with pytest.raises(FlagsmithAPIError): - flagsmith.handle_stream_event( - event=Mock( - data="{{test}}\n\n", - ) - ) - - with pytest.raises(FlagsmithAPIError): - flagsmith.handle_stream_event( - event=Mock( - data="test", - ) - ) - - -def test_invalid_timestamp_in_payload( - server_api_key: str, mocker: MockerFixture -) -> None: - mocker.patch("flagsmith.Flagsmith.update_environment") - flagsmith = Flagsmith(environment_key=server_api_key) - - with pytest.raises(FlagsmithAPIError): - flagsmith.handle_stream_event( - event=Mock( - data='{"updated_at": "test"}\n\n', - ) - ) - - with pytest.raises(FlagsmithAPIError): - flagsmith.handle_stream_event( - event=Mock( - data='{"test": "test"}\n\n', - ) - ) From e1e0210e4cf52d68d10dad230288a05146124003 Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:42:25 +0100 Subject: [PATCH 057/121] chore(main): release 3.8.0 (#96) * chore(main): release 3.8.0 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2e30867..fa4291e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.7.0" + ".": "3.8.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 695a463..3e055e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ +## [3.8.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.7.0...v3.8.0) (2024-08-12) + +### Features + +- Support transient identities and traits ([#93](https://github.com/Flagsmith/flagsmith-python-client/issues/93)) + ([0a11db5](https://github.com/Flagsmith/flagsmith-python-client/commit/0a11db5a1010c177856716e6b90292651fa5889b)) + +### Bug Fixes + +- Offline handler not used as fallback for local evaluation mode during init + ([#100](https://github.com/Flagsmith/flagsmith-python-client/issues/100)) + ([6f6d595](https://github.com/Flagsmith/flagsmith-python-client/commit/6f6d5950bc3a6befd953dc1a24ef497a4a018c7b)) + ## [v3.7.0](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.7.0) - 17 Jul 2024 ### What's Changed From a2136d7cb73e819da8a7a08ab98a3c7bfaa52df9 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 12 Aug 2024 16:56:25 +0100 Subject: [PATCH 058/121] fix: Flaky `test_offline_mode__local_evaluation__correct_fallback` (#103) --- .pre-commit-config.yaml | 2 +- poetry.lock | 41 ++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + tests/test_flagsmith.py | 6 ++---- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e30251..c5fb0cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: mypy args: [--strict] additional_dependencies: - [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] + [freezegun, pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] - repo: https://github.com/PyCQA/isort rev: 5.13.2 hooks: diff --git a/poetry.lock b/poetry.lock index d317563..3a67ab0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -289,6 +289,20 @@ pydantic = ">=2.3.0,<3" pydantic-collections = ">=0.5.1,<1" semver = ">=3.0.1" +[[package]] +name = "freezegun" +version = "1.5.1" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.7" +files = [ + {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, + {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + [[package]] name = "identify" version = "2.6.0" @@ -591,6 +605,20 @@ pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "pyyaml" version = "6.0.2" @@ -721,6 +749,17 @@ files = [ {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, ] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "sseclient-py" version = "1.8.0" @@ -808,4 +847,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "271e7bd94df6cfdf332704f14a7fe4fab36d58ba7e2d423f0cc242c5b53d91fc" +content-hash = "349611a03a029c09eddd68de10599fc1d97fc8ecd826d9fc7f0b5be15ba98c0f" diff --git a/pyproject.toml b/pyproject.toml index 2dcdeb9..5bc92ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ pytest-mock = "^3.6.1" pre-commit = "^2.17.0" responses = "^0.24.1" types-requests = "^2.32" +freezegun = "^1.5.1" [tool.mypy] plugins = ["pydantic.mypy"] diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index a76b6fe..327f0d5 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -3,6 +3,7 @@ import typing import uuid +import freezegun import pytest import requests import responses @@ -527,6 +528,7 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( @responses.activate() +@freezegun.freeze_time() def test_offline_mode__local_evaluation__correct_fallback( mocker: MockerFixture, environment_model: EnvironmentModel, @@ -546,10 +548,6 @@ def test_offline_mode__local_evaluation__correct_fallback( offline_handler=mock_offline_handler, ) - # We only verify local evaluation mode init phase here. - # Prevent additional API calls. - flagsmith.environment_data_polling_manager_thread.stop() - # When environment_flags = flagsmith.get_environment_flags() identity_flags = flagsmith.get_identity_flags("identity", traits={}) From 840bc0e33803a66af2342ec7ff0053744ada603d Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 12 Aug 2024 16:56:47 +0100 Subject: [PATCH 059/121] fix: Package version not bumped during automatic release (#102) --- release-please-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-please-config.json b/release-please-config.json index 0a099a3..4c84fbd 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -2,7 +2,7 @@ "bootstrap-sha": "fd2094131e5e4972a17be5a8efa4cb379cc821ee", "packages": { ".": { - "release-type": "simple", + "release-type": "python", "changelog-path": "CHANGELOG.md", "bump-minor-pre-major": false, "bump-patch-for-minor-pre-major": false, From 8d69ab0ef627551a80895aa974b6c95261c7b09c Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 12 Aug 2024 18:40:44 +0100 Subject: [PATCH 060/121] chore: single source for release-please release type (#106) * chore: single source for release-please release type * chore: attempt fixing flaky test again * chore: reset changelog to 3.7.0 --- .github/workflows/release-please.yml | 1 - .pre-commit-config.yaml | 2 +- .release-please-manifest.json | 2 +- CHANGELOG.md | 15 ---------- poetry.lock | 41 +--------------------------- pyproject.toml | 1 - tests/test_flagsmith.py | 4 +-- 7 files changed, 5 insertions(+), 61 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 187026e..968c75b 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -16,4 +16,3 @@ jobs: - uses: googleapis/release-please-action@v4 with: token: ${{ secrets.RELEASE_PLEASE_GITHUB_TOKEN }} - release-type: simple diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5fb0cb..8e30251 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: mypy args: [--strict] additional_dependencies: - [freezegun, pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] + [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] - repo: https://github.com/PyCQA/isort rev: 5.13.2 hooks: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fa4291e..2e30867 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.8.0" + ".": "3.7.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e055e4..fd90f82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,5 @@ # Changelog - - -## [3.8.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.7.0...v3.8.0) (2024-08-12) - -### Features - -- Support transient identities and traits ([#93](https://github.com/Flagsmith/flagsmith-python-client/issues/93)) - ([0a11db5](https://github.com/Flagsmith/flagsmith-python-client/commit/0a11db5a1010c177856716e6b90292651fa5889b)) - -### Bug Fixes - -- Offline handler not used as fallback for local evaluation mode during init - ([#100](https://github.com/Flagsmith/flagsmith-python-client/issues/100)) - ([6f6d595](https://github.com/Flagsmith/flagsmith-python-client/commit/6f6d5950bc3a6befd953dc1a24ef497a4a018c7b)) - ## [v3.7.0](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.7.0) - 17 Jul 2024 ### What's Changed diff --git a/poetry.lock b/poetry.lock index 3a67ab0..d317563 100644 --- a/poetry.lock +++ b/poetry.lock @@ -289,20 +289,6 @@ pydantic = ">=2.3.0,<3" pydantic-collections = ">=0.5.1,<1" semver = ">=3.0.1" -[[package]] -name = "freezegun" -version = "1.5.1" -description = "Let your Python tests travel through time" -optional = false -python-versions = ">=3.7" -files = [ - {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, - {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, -] - -[package.dependencies] -python-dateutil = ">=2.7" - [[package]] name = "identify" version = "2.6.0" @@ -605,20 +591,6 @@ pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - [[package]] name = "pyyaml" version = "6.0.2" @@ -749,17 +721,6 @@ files = [ {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, ] -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - [[package]] name = "sseclient-py" version = "1.8.0" @@ -847,4 +808,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "349611a03a029c09eddd68de10599fc1d97fc8ecd826d9fc7f0b5be15ba98c0f" +content-hash = "271e7bd94df6cfdf332704f14a7fe4fab36d58ba7e2d423f0cc242c5b53d91fc" diff --git a/pyproject.toml b/pyproject.toml index 5bc92ce..2dcdeb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,6 @@ pytest-mock = "^3.6.1" pre-commit = "^2.17.0" responses = "^0.24.1" types-requests = "^2.32" -freezegun = "^1.5.1" [tool.mypy] plugins = ["pydantic.mypy"] diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 327f0d5..a324427 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -3,7 +3,6 @@ import typing import uuid -import freezegun import pytest import requests import responses @@ -528,7 +527,6 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( @responses.activate() -@freezegun.freeze_time() def test_offline_mode__local_evaluation__correct_fallback( mocker: MockerFixture, environment_model: EnvironmentModel, @@ -539,6 +537,8 @@ def test_offline_mode__local_evaluation__correct_fallback( mock_offline_handler = mocker.MagicMock(spec=BaseOfflineHandler) mock_offline_handler.get_environment.return_value = environment_model + mocker.patch("flagsmith.flagsmith.EnvironmentDataPollingManager") + responses.get(api_url + "environment-document/", status=500) flagsmith = Flagsmith( From 56cdcc1c9f0596bb40466fd4d42c0a30fbb6633b Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:42:35 +0100 Subject: [PATCH 061/121] chore(main): release 3.8.0 (#107) * chore(main): release 3.8.0 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2e30867..fa4291e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.7.0" + ".": "3.8.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index fd90f82..f2b0ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [3.8.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.7.0...v3.8.0) (2024-08-12) + +### Features + +- Support transient identities and traits ([#93](https://github.com/Flagsmith/flagsmith-python-client/issues/93)) + ([0a11db5](https://github.com/Flagsmith/flagsmith-python-client/commit/0a11db5a1010c177856716e6b90292651fa5889b)) + +### Bug Fixes + +- Flaky `test_offline_mode__local_evaluation__correct_fallback` + ([#103](https://github.com/Flagsmith/flagsmith-python-client/issues/103)) + ([a2136d7](https://github.com/Flagsmith/flagsmith-python-client/commit/a2136d7cb73e819da8a7a08ab98a3c7bfaa52df9)) +- Offline handler not used as fallback for local evaluation mode during init + ([#100](https://github.com/Flagsmith/flagsmith-python-client/issues/100)) + ([6f6d595](https://github.com/Flagsmith/flagsmith-python-client/commit/6f6d5950bc3a6befd953dc1a24ef497a4a018c7b)) +- Package version not bumped during automatic release + ([#102](https://github.com/Flagsmith/flagsmith-python-client/issues/102)) + ([840bc0e](https://github.com/Flagsmith/flagsmith-python-client/commit/840bc0e33803a66af2342ec7ff0053744ada603d)) + ## [v3.7.0](https://github.com/Flagsmith/flagsmith-python-client/releases/tag/v3.7.0) - 17 Jul 2024 ### What's Changed diff --git a/pyproject.toml b/pyproject.toml index 2dcdeb9..cd9aa56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.7.0" +version = "3.8.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 4ad5941a000802ddf8b38ba76db791b4dee8092f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:27:11 +0100 Subject: [PATCH 062/121] [pre-commit.ci] pre-commit autoupdate (#110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e30251..f04136c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.1 + rev: v1.11.2 hooks: - id: mypy args: [--strict] From d59b7a3f04f30bd118d1d8c24ba0e6b41804a2d5 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Mon, 16 Dec 2024 10:34:37 +0000 Subject: [PATCH 063/121] Fix conventional commit labelling permissions (#114) --- .github/workflows/pull-request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 9938d50..3344120 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -8,6 +8,7 @@ jobs: name: Conventional Commit runs-on: ubuntu-latest permissions: + contents: read pull-requests: write steps: - name: Check PR Conventional Commit title From 1162494c20ca218c902f9f348369c265138a140d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Mon, 10 Mar 2025 16:42:40 -0300 Subject: [PATCH 064/121] Add utility functions for webhooks --- flagsmith/__init__.py | 3 ++- flagsmith/webhooks.py | 40 +++++++++++++++++++++++++++++++++ tests/test_webhooks.py | 50 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 flagsmith/webhooks.py create mode 100644 tests/test_webhooks.py diff --git a/flagsmith/__init__.py b/flagsmith/__init__.py index 4fbd5fc..f576701 100644 --- a/flagsmith/__init__.py +++ b/flagsmith/__init__.py @@ -1,3 +1,4 @@ +from . import webhooks from .flagsmith import Flagsmith -__all__ = ("Flagsmith",) +__all__ = ("Flagsmith", "webhooks") diff --git a/flagsmith/webhooks.py b/flagsmith/webhooks.py new file mode 100644 index 0000000..31ac77b --- /dev/null +++ b/flagsmith/webhooks.py @@ -0,0 +1,40 @@ +import hashlib +import hmac + + +def generate_signature( + request_body: str | bytes, + shared_secret: str, +) -> str: + """Generates a signature for a webhook request body using HMAC-SHA256. + + :param request_body: The raw request body, as string or bytes. + :param shared_secret: The shared secret configured for this specific webhook. + :return: The hex-encoded signature. + """ + if isinstance(request_body, str): + request_body = request_body.encode() + + shared_secret_bytes = shared_secret.encode() + + return hmac.new( + key=shared_secret_bytes, + msg=request_body, + digestmod=hashlib.sha256, + ).hexdigest() + + +def verify_signature( + request_body: str | bytes, + received_signature: str, + shared_secret: str, +) -> bool: + """Verifies a webhook's signature to determine if the request was sent by Flagsmith. + + :param request_body: The raw request body, as string or bytes. + :param received_signature: The signature as received in the X-Flagsmith-Signature request header. + :param shared_secret: The shared secret configured for this specific webhook. + :return: True if the signature is valid, False otherwise. + """ + expected_signature = generate_signature(request_body, shared_secret) + return hmac.compare_digest(expected_signature, received_signature) diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py new file mode 100644 index 0000000..0615ede --- /dev/null +++ b/tests/test_webhooks.py @@ -0,0 +1,50 @@ +import json + +from flagsmith.webhooks import generate_signature, verify_signature + + +def test_generate_signature(): + # Given + request_body = json.dumps({"data": {"foo": 123}}) + shared_secret = "shh" + + # When + signature = generate_signature(request_body, shared_secret) + + # Then + assert isinstance(signature, str) + assert len(signature) == 64 # SHA-256 hex digest is 64 characters + + +def test_verify_signature_valid(): + # Given + request_body = json.dumps({"data": {"foo": 123}}) + shared_secret = "shh" + + # When + signature = generate_signature(request_body, shared_secret) + + # Then + assert verify_signature( + request_body=request_body, + received_signature=signature, + shared_secret=shared_secret, + ) + # Test with bytes instead of str + assert verify_signature( + request_body=request_body.encode(), + received_signature=signature, + shared_secret=shared_secret, + ) + + +def test_verify_signature_invalid(): + # Given + request_body = json.dumps({"event": "flag_updated", "data": {"id": 123}}) + + # Then + assert not verify_signature( + request_body=request_body, + received_signature="bad", + shared_secret="?", + ) From 9e4e8772106a06fd07bc313f3fb4c7529a8b6c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Mon, 10 Mar 2025 16:47:37 -0300 Subject: [PATCH 065/121] types --- tests/test_webhooks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py index 0615ede..5ac7ea6 100644 --- a/tests/test_webhooks.py +++ b/tests/test_webhooks.py @@ -3,7 +3,7 @@ from flagsmith.webhooks import generate_signature, verify_signature -def test_generate_signature(): +def test_generate_signature() -> None: # Given request_body = json.dumps({"data": {"foo": 123}}) shared_secret = "shh" @@ -16,7 +16,7 @@ def test_generate_signature(): assert len(signature) == 64 # SHA-256 hex digest is 64 characters -def test_verify_signature_valid(): +def test_verify_signature_valid() -> None: # Given request_body = json.dumps({"data": {"foo": 123}}) shared_secret = "shh" @@ -38,7 +38,7 @@ def test_verify_signature_valid(): ) -def test_verify_signature_invalid(): +def test_verify_signature_invalid() -> None: # Given request_body = json.dumps({"event": "flag_updated", "data": {"id": 123}}) From 9ee29e876bf4eaaf9fd91bb821efecb24b23551e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Mon, 10 Mar 2025 16:56:53 -0300 Subject: [PATCH 066/121] 3.8 types --- flagsmith/webhooks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flagsmith/webhooks.py b/flagsmith/webhooks.py index 31ac77b..ab31ca2 100644 --- a/flagsmith/webhooks.py +++ b/flagsmith/webhooks.py @@ -1,9 +1,10 @@ import hashlib import hmac +from typing import Union def generate_signature( - request_body: str | bytes, + request_body: Union[str, bytes], shared_secret: str, ) -> str: """Generates a signature for a webhook request body using HMAC-SHA256. @@ -25,7 +26,7 @@ def generate_signature( def verify_signature( - request_body: str | bytes, + request_body: Union[str, bytes], received_signature: str, shared_secret: str, ) -> bool: From 93e3f9892eda624507cb3954e2ce0fefb7f0c896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 10:35:48 +0100 Subject: [PATCH 067/121] Bump virtualenv from 20.26.3 to 20.26.6 (#117) Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.26.3 to 20.26.6. - [Release notes](https://github.com/pypa/virtualenv/releases) - [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/virtualenv/compare/20.26.3...20.26.6) --- updated-dependencies: - dependency-name: virtualenv dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 58 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index d317563..1d8aa42 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -20,6 +21,7 @@ version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, @@ -31,6 +33,7 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -42,6 +45,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "dev"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -141,6 +145,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -152,6 +158,7 @@ version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, @@ -231,7 +238,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "distlib" @@ -239,6 +246,7 @@ version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -250,6 +258,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -264,6 +274,7 @@ version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, @@ -272,7 +283,7 @@ files = [ [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] [[package]] name = "flagsmith-flag-engine" @@ -280,6 +291,7 @@ version = "5.1.1" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "flagsmith-flag-engine-5.1.1.tar.gz", hash = "sha256:a97d001ac50fcddee273a25d8c88442b33797fde5b4d657f3e83e1493aa4f536"}, ] @@ -295,6 +307,7 @@ version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, @@ -309,6 +322,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -320,6 +334,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -331,6 +346,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -342,6 +358,7 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -353,6 +370,7 @@ version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, @@ -369,6 +387,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -384,6 +403,7 @@ version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, @@ -402,6 +422,7 @@ version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, @@ -424,6 +445,7 @@ version = "0.6.0" description = "Collections of pydantic models" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pydantic_collections-0.6.0-py3-none-any.whl", hash = "sha256:ec559722abf6a0f80e6f00b3d28f0f39c0ed5feb1641166230eb75e9da880162"}, {file = "pydantic_collections-0.6.0.tar.gz", hash = "sha256:c34d3fd1df5600b315cdecdd8e74eacd4c8c607b7e3f2c9392b2a15850a4ef9e"}, @@ -439,6 +461,7 @@ version = "2.20.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, @@ -540,6 +563,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -562,6 +586,7 @@ version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -580,6 +605,7 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -597,6 +623,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -659,6 +686,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -680,6 +708,7 @@ version = "1.0.1" description = "Asynchronous Python HTTP for Humans." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "requests-futures-1.0.1.tar.gz", hash = "sha256:f55a4ef80070e2858e7d1e73123d2bfaeaf25b93fd34384d8ddf148e2b676373"}, {file = "requests_futures-1.0.1-py2.py3-none-any.whl", hash = "sha256:4a2f5472e9911a79532137d156aa937cd9cd90fec55677f71b2976d1f7a66d38"}, @@ -697,6 +726,7 @@ version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "responses-0.24.1-py3-none-any.whl", hash = "sha256:a2b43f4c08bfb9c9bd242568328c65a34b318741d3fab884ac843c5ceeb543f9"}, {file = "responses-0.24.1.tar.gz", hash = "sha256:b127c6ca3f8df0eb9cc82fd93109a3007a86acb24871834c47b77765152ecf8c"}, @@ -708,7 +738,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "semver" @@ -716,6 +746,7 @@ version = "3.0.2" description = "Python helper for Semantic Versioning (https://semver.org)" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, @@ -727,6 +758,7 @@ version = "1.8.0" description = "SSE client for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "sseclient-py-1.8.0.tar.gz", hash = "sha256:c547c5c1a7633230a38dc599a21a2dc638f9b5c297286b48b46b935c71fac3e8"}, {file = "sseclient_py-1.8.0-py2.py3-none-any.whl", hash = "sha256:4ecca6dc0b9f963f8384e9d7fd529bf93dd7d708144c4fb5da0e0a1a926fee83"}, @@ -738,6 +770,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -749,6 +783,7 @@ version = "2.32.0.20240712" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, @@ -763,6 +798,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -774,26 +810,28 @@ version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.3" +version = "20.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, + {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, + {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, ] [package.dependencies] @@ -803,9 +841,9 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.8.1,<4" content-hash = "271e7bd94df6cfdf332704f14a7fe4fab36d58ba7e2d423f0cc242c5b53d91fc" From a561d0cac7918ee5d36610265e6e941a132914ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 10:36:26 +0100 Subject: [PATCH 068/121] [pre-commit.ci] pre-commit autoupdate (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.11.2 → v1.15.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.2...v1.15.0) - [github.com/PyCQA/isort: 5.13.2 → 6.0.1](https://github.com/PyCQA/isort/compare/5.13.2...6.0.1) - [github.com/psf/black: 24.8.0 → 25.1.0](https://github.com/psf/black/compare/24.8.0...25.1.0) - [github.com/pycqa/flake8: 7.1.1 → 7.2.0](https://github.com/pycqa/flake8/compare/7.1.1...7.2.0) - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f04136c..c99a71e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,26 +1,26 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.15.0 hooks: - id: mypy args: [--strict] additional_dependencies: [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] - repo: https://github.com/PyCQA/isort - rev: 5.13.2 + rev: 6.0.1 hooks: - id: isort - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 7.1.1 + rev: 7.2.0 hooks: - id: flake8 name: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - repo: https://github.com/pre-commit/mirrors-prettier From 59b5469e7bde5694bdb196ad0e216c7b73b84e1c Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 1 Apr 2025 10:42:03 +0100 Subject: [PATCH 069/121] Add / update config files to adhere to release please requirements (#120) --- .pre-commit-config.yaml | 3 +++ dependabot.yml | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 dependabot.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c99a71e..0db070b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,3 +27,6 @@ repos: rev: v4.0.0-alpha.8 hooks: - id: prettier + +ci: + autoupdate_commit_msg: 'ci: pre-commit autoupdate' diff --git a/dependabot.yml b/dependabot.yml new file mode 100644 index 0000000..deb3bfc --- /dev/null +++ b/dependabot.yml @@ -0,0 +1,16 @@ +# $schema: https://json.schemastore.org/dependabot-2.0.json + +version: 2 +updates: + - package-ecosystem: 'pip' + # we only want security updates from dependabot, so we set the limit to 0 + # for regular updates. See documentation for further information here: + # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#open-pull-requests-limit- + open-pull-requests-limit: 0 + directory: '.' + schedule: + interval: 'daily' + reviewers: + - 'flagsmith/flagsmith-back-end' + commit-message: + prefix: 'deps' From 3ee059c0d14078fa995e846fed661fd9096bb68a Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Tue, 1 Apr 2025 10:42:25 +0100 Subject: [PATCH 070/121] chore(main): release 3.9.0 (#119) * chore(main): release 3.9.0 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fa4291e..7c3079b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.8.0" + ".": "3.9.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f2b0ce1..51b52d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [3.9.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.8.0...v3.9.0) (2025-04-01) + +### Features + +- Add utility functions for webhooks + ([3d8df11](https://github.com/Flagsmith/flagsmith-python-client/commit/3d8df11ddf4656c5f20c0f558e1d7a3af412b960)) + ## [3.8.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.7.0...v3.8.0) (2024-08-12) ### Features diff --git a/pyproject.toml b/pyproject.toml index cd9aa56..21e4c10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.8.0" +version = "3.9.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 7a335eb015171fd8ee654c70c26bebbfb3e9c168 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 1 Apr 2025 11:42:56 +0100 Subject: [PATCH 071/121] ci: replace publish workflow (#121) * Fix publish workflow * Replace publish workflow with official pypa action * fix copypasta * Re-add fetch-depth --- .github/workflows/publish.yml | 56 +++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d793046..4088886 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,24 +1,54 @@ -name: Publish PyPI Package +name: Publish to PyPI on: - push: - tags: - - '*' + release: + types: [published] + workflow_dispatch: + +env: + tag_name: ${{ github.event.release.tag_name || github.ref_name }} + artifact-name: flagsmith-python-client-${{ github.event.release.tag_name || github.ref_name }} jobs: - package: + build: runs-on: ubuntu-latest - name: Publish Pypi Package steps: - - name: Cloning repo - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Build and publish to pypi - uses: JRubics/poetry-publish@v1.10 + - uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install Poetry + run: pipx install poetry + + - name: Build Package + run: poetry build + + - uses: actions/upload-artifact@v4 + with: + name: ${{ env.artifact-name }} + path: dist/ + + - uses: softprops/action-gh-release@v2 with: - pypi_token: ${{ secrets.PYPI_API_TOKEN }} - ignore_dev_requirements: 'yes' - build_format: 'sdist' + tag_name: ${{ env.tag_name }} + files: dist/* + + publish: + runs-on: ubuntu-latest + needs: build + + permissions: + id-token: write # for pypa/gh-action-pypi-publish to authenticate with PyPI + + steps: + - uses: actions/download-artifact@v4 + with: + name: ${{ env.artifact-name }} + path: dist/ + + - uses: pypa/gh-action-pypi-publish@release/v1 From 17068db995652b9f828a3b7cebad1ef3cb4f7e6a Mon Sep 17 00:00:00 2001 From: Evandro Myller Date: Tue, 29 Apr 2025 17:14:44 -0300 Subject: [PATCH 072/121] Fix HTTP requests not timing out --- flagsmith/flagsmith.py | 2 +- tests/test_flagsmith.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index f5d6882..1d05fcc 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -49,7 +49,7 @@ def __init__( api_url: typing.Optional[str] = None, realtime_api_url: typing.Optional[str] = None, custom_headers: typing.Optional[typing.Dict[str, typing.Any]] = None, - request_timeout_seconds: typing.Optional[int] = None, + request_timeout_seconds: typing.Optional[int] = 10, enable_local_evaluation: bool = False, environment_refresh_interval_seconds: typing.Union[int, float] = 60, retries: typing.Optional[Retry] = None, diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index a324427..71f3a1b 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -278,6 +278,39 @@ def test_non_200_response_raises_flagsmith_api_error(flagsmith: Flagsmith) -> No # expected exception raised +@pytest.mark.parametrize( + "settings, expected_timeout", + [ + ({"request_timeout_seconds": 5}, 5), # Arbitrary timeout + ({"request_timeout_seconds": None}, None), # No timeout is forced + ({}, 10), # Default timeout + ], +) +def test_request_times_out_according_to_setting( + mocker: MockerFixture, + api_key: str, + settings: typing.Dict[str, typing.Any], + expected_timeout: typing.Optional[int], +) -> None: + # Given + session = mocker.patch("flagsmith.flagsmith.requests.Session").return_value + flagsmith = Flagsmith( + environment_key=api_key, + enable_local_evaluation=False, + **settings, + ) + + # When + flagsmith.get_environment_flags() + + # Then + session.get.assert_called_once_with( + "https://edge.api.flagsmith.com/api/v1/flags/", + json=None, + timeout=expected_timeout, + ) + + @responses.activate() def test_default_flag_is_used_when_no_environment_flags_returned(api_key: str) -> None: # Given From 21649a62030ecaec625f437e4d815c7818d02ec1 Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:27:52 +0100 Subject: [PATCH 073/121] chore(main): release 3.9.1 (#123) * chore(main): release 3.9.1 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7c3079b..4344650 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.9.0" + ".": "3.9.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b52d3..fede898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [3.9.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.9.0...v3.9.1) (2025-04-29) + +### Bug Fixes + +- Fix HTTP requests not timing out + ([7d61a47](https://github.com/Flagsmith/flagsmith-python-client/commit/7d61a47d0e7ec500b77bec15403f655a159e01fa)) + ## [3.9.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.8.0...v3.9.0) (2025-04-01) ### Features diff --git a/pyproject.toml b/pyproject.toml index 21e4c10..80cef5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.9.0" +version = "3.9.1" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From b67f9a6c13467c12b476093362a8606b978f7456 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 23 May 2025 14:47:17 +0100 Subject: [PATCH 074/121] chore: Add workflow to add new issues to engineering project (#124) --- .../add-issues-to-engineering-project.yml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/add-issues-to-engineering-project.yml diff --git a/.github/workflows/add-issues-to-engineering-project.yml b/.github/workflows/add-issues-to-engineering-project.yml new file mode 100644 index 0000000..e6bd968 --- /dev/null +++ b/.github/workflows/add-issues-to-engineering-project.yml @@ -0,0 +1,32 @@ +name: Add New Issue to Engineering Project + +on: + issues: + types: [opened] + +jobs: + add_issue_to_project: + runs-on: ubuntu-latest + steps: + - name: Add issue to project + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GH_TOKEN_ADD_TO_ENG_PROJECT }} + script: | + const issueId = context.payload.issue.node_id; + const projectId = '22'; + + const mutation = ` + mutation { + addProjectV2ItemById(input: { + projectId: "${projectId}", + contentId: "${issueId}" + }) { + item { + id + } + } + } + `; + + await github.graphql(mutation); From 3bd7943439e880b270d7c1303f1606c9504cf402 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 23 May 2025 14:58:11 +0100 Subject: [PATCH 075/121] chore(actions): Move project id to a var (#126) --- .github/workflows/add-issues-to-engineering-project.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/add-issues-to-engineering-project.yml b/.github/workflows/add-issues-to-engineering-project.yml index e6bd968..0e820de 100644 --- a/.github/workflows/add-issues-to-engineering-project.yml +++ b/.github/workflows/add-issues-to-engineering-project.yml @@ -14,12 +14,11 @@ jobs: github-token: ${{ secrets.GH_TOKEN_ADD_TO_ENG_PROJECT }} script: | const issueId = context.payload.issue.node_id; - const projectId = '22'; const mutation = ` mutation { addProjectV2ItemById(input: { - projectId: "${projectId}", + projectId: "${{ vars.ENGINEERING_PROJECT_ID }}", contentId: "${issueId}" }) { item { From 80af1d8d4cc848029963d1f5fc55fe1e0feced0e Mon Sep 17 00:00:00 2001 From: Robert Norrie Date: Mon, 30 Jun 2025 12:06:59 +0100 Subject: [PATCH 076/121] docs: removing hero image from SDK readme (#131) To remove the risk of these breaking when we make changes in flagsmith/flagsmith we're removing these image references from the readme.md files of the SDK repos. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 16ff107..583de91 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ - - # Flagsmith Python SDK > Flagsmith allows you to manage feature flags and remote config across multiple projects, environments and From 62c55a996c5b3929ff1a710b95d1289482d85cd3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 07:33:46 +0100 Subject: [PATCH 077/121] ci: pre-commit autoupdate (#128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.16.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.16.1) - [github.com/pycqa/flake8: 7.2.0 → 7.3.0](https://github.com/pycqa/flake8/compare/7.2.0...7.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0db070b..dd1c372 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 + rev: v1.16.1 hooks: - id: mypy args: [--strict] @@ -15,7 +15,7 @@ repos: hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 7.2.0 + rev: 7.3.0 hooks: - id: flake8 name: flake8 From 30196369bcfb55647f1456daee9e4f6da49963a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 07:34:10 +0100 Subject: [PATCH 078/121] chore(deps): bump requests from 2.32.3 to 2.32.4 (#129) Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1d8aa42..d2da830 100644 --- a/poetry.lock +++ b/poetry.lock @@ -682,19 +682,19 @@ files = [ [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" From e684919ba7a8ab93de0fc8b7214d1e8abe664157 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 8 Jul 2025 07:57:47 +0100 Subject: [PATCH 079/121] chore(ci): show all sections in release please config (#132) --- release-please-config.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/release-please-config.json b/release-please-config.json index 4c84fbd..41d8efe 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -25,37 +25,37 @@ }, { "type": "ci", - "hidden": true, + "hidden": false, "section": "CI" }, { "type": "docs", - "hidden": true, + "hidden": false, "section": "Docs" }, { "type": "deps", - "hidden": true, + "hidden": false, "section": "Dependency Updates" }, { "type": "perf", - "hidden": true, + "hidden": false, "section": "Performance Improvements" }, { "type": "refactor", - "hidden": true, + "hidden": false, "section": "Refactoring" }, { "type": "test", - "hidden": true, + "hidden": false, "section": "Tests" }, { "type": "chore", - "hidden": true, + "hidden": false, "section": "Other" } ] From bfcd454851a2b7b772e7937b94ccf4b1cdaba401 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Tue, 8 Jul 2025 08:10:10 +0100 Subject: [PATCH 080/121] chore(deps): update flagsmith-flag-engine (#133) --- poetry.lock | 58 +++++++++----------------------------------------- pyproject.toml | 2 +- 2 files changed, 11 insertions(+), 49 deletions(-) diff --git a/poetry.lock b/poetry.lock index d2da830..8b2bdd8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,7 +6,6 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -21,7 +20,6 @@ version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, @@ -33,7 +31,6 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -45,7 +42,6 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" -groups = ["main", "dev"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -145,8 +141,6 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -158,7 +152,6 @@ version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, @@ -238,7 +231,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "distlib" @@ -246,7 +239,6 @@ version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -258,8 +250,6 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -274,7 +264,6 @@ version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, @@ -283,17 +272,16 @@ files = [ [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "flagsmith-flag-engine" -version = "5.1.1" +version = "5.3.1" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" -groups = ["main"] files = [ - {file = "flagsmith-flag-engine-5.1.1.tar.gz", hash = "sha256:a97d001ac50fcddee273a25d8c88442b33797fde5b4d657f3e83e1493aa4f536"}, + {file = "flagsmith-flag-engine-5.3.1.tar.gz", hash = "sha256:6f04cd3f8dc8ffed0454d43dc980ac521d52637e8f107eed3dadcfb5a6ee233f"}, ] [package.dependencies] @@ -307,7 +295,6 @@ version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, @@ -322,7 +309,6 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" -groups = ["main", "dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -334,7 +320,6 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -346,7 +331,6 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -358,7 +342,6 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, @@ -370,7 +353,6 @@ version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, @@ -387,7 +369,6 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -403,7 +384,6 @@ version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, @@ -422,7 +402,6 @@ version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, @@ -445,7 +424,6 @@ version = "0.6.0" description = "Collections of pydantic models" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "pydantic_collections-0.6.0-py3-none-any.whl", hash = "sha256:ec559722abf6a0f80e6f00b3d28f0f39c0ed5feb1641166230eb75e9da880162"}, {file = "pydantic_collections-0.6.0.tar.gz", hash = "sha256:c34d3fd1df5600b315cdecdd8e74eacd4c8c607b7e3f2c9392b2a15850a4ef9e"}, @@ -461,7 +439,6 @@ version = "2.20.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, @@ -563,7 +540,6 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -586,7 +562,6 @@ version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -605,7 +580,6 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -623,7 +597,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -686,7 +659,6 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -708,7 +680,6 @@ version = "1.0.1" description = "Asynchronous Python HTTP for Humans." optional = false python-versions = "*" -groups = ["main"] files = [ {file = "requests-futures-1.0.1.tar.gz", hash = "sha256:f55a4ef80070e2858e7d1e73123d2bfaeaf25b93fd34384d8ddf148e2b676373"}, {file = "requests_futures-1.0.1-py2.py3-none-any.whl", hash = "sha256:4a2f5472e9911a79532137d156aa937cd9cd90fec55677f71b2976d1f7a66d38"}, @@ -726,7 +697,6 @@ version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "responses-0.24.1-py3-none-any.whl", hash = "sha256:a2b43f4c08bfb9c9bd242568328c65a34b318741d3fab884ac843c5ceeb543f9"}, {file = "responses-0.24.1.tar.gz", hash = "sha256:b127c6ca3f8df0eb9cc82fd93109a3007a86acb24871834c47b77765152ecf8c"}, @@ -738,7 +708,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "semver" @@ -746,7 +716,6 @@ version = "3.0.2" description = "Python helper for Semantic Versioning (https://semver.org)" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, @@ -758,7 +727,6 @@ version = "1.8.0" description = "SSE client for Python" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "sseclient-py-1.8.0.tar.gz", hash = "sha256:c547c5c1a7633230a38dc599a21a2dc638f9b5c297286b48b46b935c71fac3e8"}, {file = "sseclient_py-1.8.0-py2.py3-none-any.whl", hash = "sha256:4ecca6dc0b9f963f8384e9d7fd529bf93dd7d708144c4fb5da0e0a1a926fee83"}, @@ -770,8 +738,6 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" -groups = ["dev"] -markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -783,7 +749,6 @@ version = "2.32.0.20240712" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, @@ -798,7 +763,6 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -810,14 +774,13 @@ version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -828,7 +791,6 @@ version = "20.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, @@ -841,9 +803,9 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = ">=3.8.1,<4" -content-hash = "271e7bd94df6cfdf332704f14a7fe4fab36d58ba7e2d423f0cc242c5b53d91fc" +content-hash = "4da7829338a4d12130b2c2b77dc2804032bab4458a6d95624faeaf64433c7516" diff --git a/pyproject.toml b/pyproject.toml index 80cef5e..58aeae9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ packages = [{ include = "flagsmith" }] python = ">=3.8.1,<4" requests = "^2.32.3" requests-futures = "^1.0.1" -flagsmith-flag-engine = "^5.1.0" +flagsmith-flag-engine = "^5.3.1" sseclient-py = "^1.8.0" pydantic = "^2" From 9ac4dc7dcc65c2906513e552d5eb189a5994c13d Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Tue, 8 Jul 2025 08:47:43 +0100 Subject: [PATCH 081/121] chore(main): release 3.9.2 (#134) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4344650..bb30c44 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.9.1" + ".": "3.9.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index fede898..3df3590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## [3.9.2](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.9.1...v3.9.2) (2025-07-08) + +### CI + +- pre-commit autoupdate ([#128](https://github.com/Flagsmith/flagsmith-python-client/issues/128)) + ([62c55a9](https://github.com/Flagsmith/flagsmith-python-client/commit/62c55a996c5b3929ff1a710b95d1289482d85cd3)) + +### Docs + +- removing hero image from SDK readme ([#131](https://github.com/Flagsmith/flagsmith-python-client/issues/131)) + ([80af1d8](https://github.com/Flagsmith/flagsmith-python-client/commit/80af1d8d4cc848029963d1f5fc55fe1e0feced0e)) + +### Other + +- **actions:** Move project id to a var ([#126](https://github.com/Flagsmith/flagsmith-python-client/issues/126)) + ([3bd7943](https://github.com/Flagsmith/flagsmith-python-client/commit/3bd7943439e880b270d7c1303f1606c9504cf402)) +- Add workflow to add new issues to engineering project + ([#124](https://github.com/Flagsmith/flagsmith-python-client/issues/124)) + ([b67f9a6](https://github.com/Flagsmith/flagsmith-python-client/commit/b67f9a6c13467c12b476093362a8606b978f7456)) +- **ci:** show all sections in release please config + ([#132](https://github.com/Flagsmith/flagsmith-python-client/issues/132)) + ([e684919](https://github.com/Flagsmith/flagsmith-python-client/commit/e684919ba7a8ab93de0fc8b7214d1e8abe664157)) +- **deps:** bump requests from 2.32.3 to 2.32.4 + ([#129](https://github.com/Flagsmith/flagsmith-python-client/issues/129)) + ([3019636](https://github.com/Flagsmith/flagsmith-python-client/commit/30196369bcfb55647f1456daee9e4f6da49963a7)) +- **deps:** update flagsmith-flag-engine ([#133](https://github.com/Flagsmith/flagsmith-python-client/issues/133)) + ([bfcd454](https://github.com/Flagsmith/flagsmith-python-client/commit/bfcd454851a2b7b772e7937b94ccf4b1cdaba401)) + ## [3.9.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.9.0...v3.9.1) (2025-04-29) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index 58aeae9..1cf9c6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.9.1" +version = "3.9.2" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 441c46a0ae72f1ecb8d6e0b4b82f24706c34f942 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 21 Jul 2025 20:17:57 +0100 Subject: [PATCH 082/121] feat: Support SDK metrics (#136) --- .github/workflows/pytest.yml | 4 + .pre-commit-config.yaml | 7 -- flagsmith/__init__.py | 7 +- flagsmith/flagsmith.py | 41 +++++++++- flagsmith/types.py | 7 +- flagsmith/version.py | 3 + poetry.lock | 153 ++++++++++++++++++++++++++++++++--- pyproject.toml | 3 +- tests/test_flagsmith.py | 94 ++++++++++++++++++++- 9 files changed, 292 insertions(+), 27 deletions(-) create mode 100644 flagsmith/version.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 743f3a7..8cc83d3 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -32,5 +32,9 @@ jobs: pip install poetry poetry install --with dev + - name: Check for new typing errors + if: ${{ matrix.python-version != '3.8' }} + run: poetry run mypy --strict . + - name: Run Tests run: poetry run pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dd1c372..f295f10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,4 @@ repos: - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.16.1 - hooks: - - id: mypy - args: [--strict] - additional_dependencies: - [pydantic, pytest, pytest_mock, types-requests, flagsmith-flag-engine, responses, sseclient-py] - repo: https://github.com/PyCQA/isort rev: 6.0.1 hooks: diff --git a/flagsmith/__init__.py b/flagsmith/__init__.py index f576701..41473e4 100644 --- a/flagsmith/__init__.py +++ b/flagsmith/__init__.py @@ -1,4 +1,5 @@ -from . import webhooks -from .flagsmith import Flagsmith +from flagsmith import webhooks +from flagsmith.flagsmith import Flagsmith +from flagsmith.version import __version__ -__all__ = ("Flagsmith", "webhooks") +__all__ = ("Flagsmith", "webhooks", "__version__") diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 1d05fcc..5ed7404 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -1,4 +1,5 @@ import logging +import sys import typing from datetime import timezone @@ -11,6 +12,7 @@ from flag_engine.identities.traits.types import TraitValue from flag_engine.segments.evaluator import get_identity_segments from requests.adapters import HTTPAdapter +from requests.utils import default_user_agent from urllib3 import Retry from flagsmith.analytics import AnalyticsProcessor @@ -19,13 +21,24 @@ from flagsmith.offline_handlers import BaseOfflineHandler from flagsmith.polling_manager import EnvironmentDataPollingManager from flagsmith.streaming_manager import EventStreamManager, StreamEvent -from flagsmith.types import JsonType, TraitConfig, TraitMapping +from flagsmith.types import ( + ApplicationMetadata, + JsonType, + TraitConfig, + TraitMapping, +) from flagsmith.utils.identities import generate_identity_data +from flagsmith.version import __version__ logger = logging.getLogger(__name__) DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/" DEFAULT_REALTIME_API_URL = "https://realtime.flagsmith.com/" +DEFAULT_USER_AGENT = ( + f"flagsmith-python-client/{__version__} " + + default_user_agent() + + f" python/{sys.version_info.major}.{sys.version_info.minor}" +) class Flagsmith: @@ -61,6 +74,7 @@ def __init__( offline_mode: bool = False, offline_handler: typing.Optional[BaseOfflineHandler] = None, enable_realtime_updates: bool = False, + application_metadata: typing.Optional[ApplicationMetadata] = None, ): """ :param environment_key: The environment key obtained from Flagsmith interface. @@ -88,6 +102,7 @@ def __init__( document from another source when in offline_mode. Works in place of default_flag_handler if offline_mode is not set and using remote evaluation. :param enable_realtime_updates: Use real-time functionality via SSE as opposed to polling the API + :param application_metadata: Optional metadata about the client application. """ self.offline_mode = offline_mode @@ -122,7 +137,11 @@ def __init__( self.session = requests.Session() self.session.headers.update( - **{"X-Environment-Key": environment_key}, **(custom_headers or {}) + self._get_headers( + environment_key=environment_key, + application_metadata=application_metadata, + custom_headers=custom_headers, + ) ) self.session.proxies.update(proxies or {}) retries = retries or Retry(total=3, backoff_factor=0.1) @@ -275,6 +294,24 @@ def update_environment(self) -> None: identity.identifier: identity for identity in overrides } + def _get_headers( + self, + environment_key: str, + application_metadata: typing.Optional[ApplicationMetadata], + custom_headers: typing.Optional[typing.Dict[str, typing.Any]], + ) -> typing.Dict[str, str]: + headers = { + "X-Environment-Key": environment_key, + "User-Agent": DEFAULT_USER_AGENT, + } + if application_metadata: + if name := application_metadata.get("name"): + headers["Flagsmith-Application-Name"] = name + if version := application_metadata.get("version"): + headers["Flagsmith-Application-Version"] = version + headers.update(custom_headers or {}) + return headers + def _get_environment_from_api(self) -> EnvironmentModel: environment_data = self._get_json_response(self.environment_url, method="GET") return EnvironmentModel.model_validate(environment_data) diff --git a/flagsmith/types.py b/flagsmith/types.py index b2a41a3..c0d535a 100644 --- a/flagsmith/types.py +++ b/flagsmith/types.py @@ -1,7 +1,7 @@ import typing from flag_engine.identities.traits.types import TraitValue -from typing_extensions import TypeAlias +from typing_extensions import NotRequired, TypeAlias _JsonScalarType: TypeAlias = typing.Union[ int, @@ -23,3 +23,8 @@ class TraitConfig(typing.TypedDict): TraitMapping: TypeAlias = typing.Mapping[str, typing.Union[TraitValue, TraitConfig]] + + +class ApplicationMetadata(typing.TypedDict): + name: NotRequired[str] + version: NotRequired[str] diff --git a/flagsmith/version.py b/flagsmith/version.py new file mode 100644 index 0000000..aa2c58c --- /dev/null +++ b/flagsmith/version.py @@ -0,0 +1,3 @@ +from importlib.metadata import version + +__version__ = version("flagsmith") diff --git a/poetry.lock b/poetry.lock index 8b2bdd8..d5b1228 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -20,6 +21,7 @@ version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, @@ -31,6 +33,8 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -42,6 +46,7 @@ version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" +groups = ["main", "dev"] files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, @@ -141,6 +146,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -152,6 +159,7 @@ version = "7.6.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, @@ -231,7 +239,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "distlib" @@ -239,6 +247,8 @@ version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, @@ -250,6 +260,8 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -264,6 +276,8 @@ version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, @@ -272,7 +286,7 @@ files = [ [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] [[package]] name = "flagsmith-flag-engine" @@ -280,6 +294,7 @@ version = "5.3.1" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "flagsmith-flag-engine-5.3.1.tar.gz", hash = "sha256:6f04cd3f8dc8ffed0454d43dc980ac521d52637e8f107eed3dadcfb5a6ee233f"}, ] @@ -295,6 +310,8 @@ version = "2.6.0" description = "File identification library for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, @@ -309,6 +326,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -320,17 +338,89 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "mypy" +version = "1.17.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6"}, + {file = "mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d"}, + {file = "mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b"}, + {file = "mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a"}, + {file = "mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f"}, + {file = "mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937"}, + {file = "mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be"}, + {file = "mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61"}, + {file = "mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f"}, + {file = "mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d"}, + {file = "mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3"}, + {file = "mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70"}, + {file = "mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb"}, + {file = "mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d"}, + {file = "mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8"}, + {file = "mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e"}, + {file = "mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8"}, + {file = "mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d"}, + {file = "mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06"}, + {file = "mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a"}, + {file = "mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889"}, + {file = "mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba"}, + {file = "mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658"}, + {file = "mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c"}, + {file = "mypy-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:63e751f1b5ab51d6f3d219fe3a2fe4523eaa387d854ad06906c63883fde5b1ab"}, + {file = "mypy-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fb09d05e0f1c329a36dcd30e27564a3555717cde87301fae4fb542402ddfad"}, + {file = "mypy-1.17.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b72c34ce05ac3a1361ae2ebb50757fb6e3624032d91488d93544e9f82db0ed6c"}, + {file = "mypy-1.17.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434ad499ad8dde8b2f6391ddfa982f41cb07ccda8e3c67781b1bfd4e5f9450a8"}, + {file = "mypy-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f105f61a5eff52e137fd73bee32958b2add9d9f0a856f17314018646af838e97"}, + {file = "mypy-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:ba06254a5a22729853209550d80f94e28690d5530c661f9416a68ac097b13fc4"}, + {file = "mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496"}, + {file = "mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + [[package]] name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -342,17 +432,33 @@ version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + [[package]] name = "platformdirs" version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, @@ -369,6 +475,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -380,13 +487,15 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.21.0" +version = "4.2.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ - {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, - {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, + {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, + {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, ] [package.dependencies] @@ -402,6 +511,7 @@ version = "2.8.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, @@ -424,6 +534,7 @@ version = "0.6.0" description = "Collections of pydantic models" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pydantic_collections-0.6.0-py3-none-any.whl", hash = "sha256:ec559722abf6a0f80e6f00b3d28f0f39c0ed5feb1641166230eb75e9da880162"}, {file = "pydantic_collections-0.6.0.tar.gz", hash = "sha256:c34d3fd1df5600b315cdecdd8e74eacd4c8c607b7e3f2c9392b2a15850a4ef9e"}, @@ -439,6 +550,7 @@ version = "2.20.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, @@ -540,6 +652,7 @@ version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -562,6 +675,7 @@ version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -580,6 +694,7 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -597,6 +712,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -659,6 +775,7 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -680,6 +797,7 @@ version = "1.0.1" description = "Asynchronous Python HTTP for Humans." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "requests-futures-1.0.1.tar.gz", hash = "sha256:f55a4ef80070e2858e7d1e73123d2bfaeaf25b93fd34384d8ddf148e2b676373"}, {file = "requests_futures-1.0.1-py2.py3-none-any.whl", hash = "sha256:4a2f5472e9911a79532137d156aa937cd9cd90fec55677f71b2976d1f7a66d38"}, @@ -697,6 +815,7 @@ version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "responses-0.24.1-py3-none-any.whl", hash = "sha256:a2b43f4c08bfb9c9bd242568328c65a34b318741d3fab884ac843c5ceeb543f9"}, {file = "responses-0.24.1.tar.gz", hash = "sha256:b127c6ca3f8df0eb9cc82fd93109a3007a86acb24871834c47b77765152ecf8c"}, @@ -708,7 +827,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "semver" @@ -716,6 +835,7 @@ version = "3.0.2" description = "Python helper for Semantic Versioning (https://semver.org)" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, @@ -727,6 +847,7 @@ version = "1.8.0" description = "SSE client for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "sseclient-py-1.8.0.tar.gz", hash = "sha256:c547c5c1a7633230a38dc599a21a2dc638f9b5c297286b48b46b935c71fac3e8"}, {file = "sseclient_py-1.8.0-py2.py3-none-any.whl", hash = "sha256:4ecca6dc0b9f963f8384e9d7fd529bf93dd7d708144c4fb5da0e0a1a926fee83"}, @@ -738,6 +859,8 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -749,6 +872,7 @@ version = "2.32.0.20240712" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, @@ -763,10 +887,12 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +markers = {dev = "python_version >= \"3.9\""} [[package]] name = "urllib3" @@ -774,13 +900,14 @@ version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -791,6 +918,8 @@ version = "20.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version >= \"3.9\"" files = [ {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, @@ -803,9 +932,9 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.8.1,<4" -content-hash = "4da7829338a4d12130b2c2b77dc2804032bab4458a6d95624faeaf64433c7516" +content-hash = "4e63cf3ee6115e58105337de9da451766f8381bf707134ed383848d4dbeae454" diff --git a/pyproject.toml b/pyproject.toml index 1cf9c6d..e09349c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,10 +21,11 @@ pydantic = "^2" optional = true [tool.poetry.group.dev.dependencies] +mypy = { version = "^1.16.1", python = ">=3.9,<4" } pytest = "^7.4.0" pytest-cov = "^4.1.0" pytest-mock = "^3.6.1" -pre-commit = "^2.17.0" +pre-commit = { version = "^4.2.0", python = ">=3.9,<4" } responses = "^0.24.1" types-requests = "^2.32" diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 71f3a1b..9ad1c84 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -1,4 +1,5 @@ import json +import sys import time import typing import uuid @@ -11,7 +12,7 @@ from pytest_mock import MockerFixture from responses import matchers -from flagsmith import Flagsmith +from flagsmith import Flagsmith, __version__ from flagsmith.exceptions import ( FlagsmithAPIError, FlagsmithFeatureDoesNotExistError, @@ -717,3 +718,94 @@ def test_custom_feature_error_raised_when_invalid_feature( with pytest.raises(FlagsmithFeatureDoesNotExistError): # When flags.is_feature_enabled("non-existing-feature") + + +@pytest.mark.parametrize( + "kwargs,expected_headers", + [ + ( + { + "environment_key": "test-key", + "application_metadata": {"name": "test-app", "version": "1.0.0"}, + }, + { + "Flagsmith-Application-Name": "test-app", + "Flagsmith-Application-Version": "1.0.0", + "X-Environment-Key": "test-key", + }, + ), + ( + { + "environment_key": "test-key", + "application_metadata": {"name": "test-app"}, + }, + { + "Flagsmith-Application-Name": "test-app", + "X-Environment-Key": "test-key", + }, + ), + ( + { + "environment_key": "test-key", + "application_metadata": {"version": "1.0.0"}, + }, + { + "Flagsmith-Application-Version": "1.0.0", + "X-Environment-Key": "test-key", + }, + ), + ( + { + "environment_key": "test-key", + "application_metadata": {"version": "1.0.0"}, + "custom_headers": {"X-Custom-Header": "CustomValue"}, + }, + { + "Flagsmith-Application-Version": "1.0.0", + "X-Environment-Key": "test-key", + "X-Custom-Header": "CustomValue", + }, + ), + ( + { + "environment_key": "test-key", + "application_metadata": None, + "custom_headers": {"X-Custom-Header": "CustomValue"}, + }, + { + "X-Environment-Key": "test-key", + "X-Custom-Header": "CustomValue", + }, + ), + ( + {"environment_key": "test-key"}, + { + "X-Environment-Key": "test-key", + }, + ), + ], +) +@responses.activate() +def test_flagsmith__init__expected_headers_sent( + kwargs: typing.Dict[str, typing.Any], + expected_headers: typing.Dict[str, str], +) -> None: + # Given + flagsmith = Flagsmith(**kwargs) + responses.add(method="GET", url=flagsmith.environment_flags_url, body="{}") + + # When + flagsmith.get_environment_flags() + + # Then + headers = responses.calls[0].request.headers + assert headers == { + "User-Agent": ( + f"flagsmith-python-client/{__version__} python-requests/{requests.__version__} " + f"python/{sys.version_info.major}.{sys.version_info.minor}" + ), + "Accept-Encoding": "gzip, deflate", + "Accept": "*/*", + "Connection": "keep-alive", + **expected_headers, + } From 2cd2435d4f3872ea399ebeacadaee8c943707a1a Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Wed, 6 Aug 2025 17:45:09 +0200 Subject: [PATCH 083/121] chore: bump-flagsmith-engine-version (#139) --- flagsmith/flagsmith.py | 4 +++- poetry.lock | 7 ++++--- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 5ed7404..f52a98f 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -280,7 +280,9 @@ def get_identity_segments( traits = traits or {} identity_model = self._get_identity_model(identifier, **traits) - segment_models = get_identity_segments(self._environment, identity_model) + segment_models = get_identity_segments( + environment=self._environment, identity=identity_model + ) return [Segment(id=sm.id, name=sm.name) for sm in segment_models] def update_environment(self) -> None: diff --git a/poetry.lock b/poetry.lock index d5b1228..506b5b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -290,13 +290,14 @@ typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] [[package]] name = "flagsmith-flag-engine" -version = "5.3.1" +version = "6.0.2" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith-flag-engine-5.3.1.tar.gz", hash = "sha256:6f04cd3f8dc8ffed0454d43dc980ac521d52637e8f107eed3dadcfb5a6ee233f"}, + {file = "flagsmith_flag_engine-6.0.2-py3-none-any.whl", hash = "sha256:dcf104acab328a397c9dae883eefe12bee430033b250995bfcb34a1c5326cde2"}, + {file = "flagsmith_flag_engine-6.0.2.tar.gz", hash = "sha256:0bf82cee6498c2aa2133655e66aba0013babaa0e2503d87123637cc562eab8c9"}, ] [package.dependencies] @@ -937,4 +938,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.8.1,<4" -content-hash = "4e63cf3ee6115e58105337de9da451766f8381bf707134ed383848d4dbeae454" +content-hash = "6d2433e6b79094dc094e2693ae2f5cb08fe78203b11dbb2be7fa5d68416a0efd" diff --git a/pyproject.toml b/pyproject.toml index e09349c..7a78641 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ packages = [{ include = "flagsmith" }] python = ">=3.8.1,<4" requests = "^2.32.3" requests-futures = "^1.0.1" -flagsmith-flag-engine = "^5.3.1" +flagsmith-flag-engine = "^6.0.2" sseclient-py = "^1.8.0" pydantic = "^2" From b6d2c843010971d803702080c284880f5040d57d Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Thu, 7 Aug 2025 08:17:02 +0100 Subject: [PATCH 084/121] chore(main): release 3.10.0 (#138) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bb30c44..fc62d3d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.9.2" + ".": "3.10.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df3590..7bf41f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [3.10.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.9.2...v3.10.0) (2025-08-06) + +### Features + +- Support SDK metrics ([#136](https://github.com/Flagsmith/flagsmith-python-client/issues/136)) + ([441c46a](https://github.com/Flagsmith/flagsmith-python-client/commit/441c46a0ae72f1ecb8d6e0b4b82f24706c34f942)) + +### Other + +- bump-flagsmith-engine-version ([#139](https://github.com/Flagsmith/flagsmith-python-client/issues/139)) + ([2cd2435](https://github.com/Flagsmith/flagsmith-python-client/commit/2cd2435d4f3872ea399ebeacadaee8c943707a1a)) + ## [3.9.2](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.9.1...v3.9.2) (2025-07-08) ### CI diff --git a/pyproject.toml b/pyproject.toml index 7a78641..7729285 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.9.2" +version = "3.10.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 03715abc403eeface2bd4d3d472b6da97d7d0e77 Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Tue, 19 Aug 2025 15:26:53 +0200 Subject: [PATCH 085/121] chore: replacing-deprecated-methods (#143) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- flagsmith/flagsmith.py | 47 ++- flagsmith/models.py | 37 ++- poetry.lock | 637 +++++++++++++++++++++------------------- pyproject.toml | 2 +- tests/test_flagsmith.py | 34 ++- tests/test_models.py | 126 ++++++++ 6 files changed, 544 insertions(+), 339 deletions(-) create mode 100644 tests/test_models.py diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index f52a98f..71ecf8d 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -6,11 +6,11 @@ import pydantic import requests from flag_engine import engine +from flag_engine.context.mappers import map_environment_identity_to_context from flag_engine.environments.models import EnvironmentModel from flag_engine.identities.models import IdentityModel from flag_engine.identities.traits.models import TraitModel from flag_engine.identities.traits.types import TraitValue -from flag_engine.segments.evaluator import get_identity_segments from requests.adapters import HTTPAdapter from requests.utils import default_user_agent from urllib3 import Retry @@ -280,10 +280,18 @@ def get_identity_segments( traits = traits or {} identity_model = self._get_identity_model(identifier, **traits) - segment_models = get_identity_segments( - environment=self._environment, identity=identity_model + context = map_environment_identity_to_context( + environment=self._environment, + identity=identity_model, + override_traits=None, ) - return [Segment(id=sm.id, name=sm.name) for sm in segment_models] + evaluation_result = engine.get_evaluation_result( + context=context, + ) + return [ + Segment(id=int(sm["key"]), name=sm["name"]) + for sm in evaluation_result.get("segments", []) + ] def update_environment(self) -> None: try: @@ -321,8 +329,18 @@ def _get_environment_from_api(self) -> EnvironmentModel: def _get_environment_flags_from_document(self) -> Flags: if self._environment is None: raise TypeError("No environment present") - return Flags.from_feature_state_models( - feature_states=engine.get_environment_feature_states(self._environment), + identity = self._get_identity_model(identifier="", traits=None) + + context = map_environment_identity_to_context( + environment=self._environment, + identity=identity, + override_traits=None, + ) + + evaluation_result = engine.get_evaluation_result(context=context) + + return Flags.from_evaluation_result( + evaluation_result=evaluation_result, analytics_processor=self._analytics_processor, default_flag_handler=self.default_flag_handler, ) @@ -333,13 +351,20 @@ def _get_identity_flags_from_document( identity_model = self._get_identity_model(identifier, **traits) if self._environment is None: raise TypeError("No environment present") - feature_states = engine.get_identity_feature_states( - self._environment, identity_model + + context = map_environment_identity_to_context( + environment=self._environment, + identity=identity_model, + override_traits=None, ) - return Flags.from_feature_state_models( - feature_states=feature_states, + + evaluation_result = engine.get_evaluation_result( + context=context, + ) + + return Flags.from_evaluation_result( + evaluation_result=evaluation_result, analytics_processor=self._analytics_processor, - identity_id=identity_model.composite_key, default_flag_handler=self.default_flag_handler, ) diff --git a/flagsmith/models.py b/flagsmith/models.py index ac7d2cc..3a9ffed 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -3,7 +3,7 @@ import typing from dataclasses import dataclass, field -from flag_engine.features.models import FeatureStateModel +from flag_engine.result.types import EvaluationResult, FlagResult from flagsmith.analytics import AnalyticsProcessor from flagsmith.exceptions import FlagsmithFeatureDoesNotExistError @@ -27,16 +27,15 @@ class Flag(BaseFlag): is_default: bool = field(default=False) @classmethod - def from_feature_state_model( + def from_evaluation_result( cls, - feature_state_model: FeatureStateModel, - identity_id: typing.Optional[typing.Union[str, int]] = None, + flag: FlagResult, ) -> Flag: return Flag( - enabled=feature_state_model.enabled, - value=feature_state_model.get_value(identity_id=identity_id), - feature_name=feature_state_model.feature.name, - feature_id=feature_state_model.feature.id, + enabled=flag["enabled"], + value=flag["value"], + feature_name=flag["name"], + feature_id=int(flag["feature_key"]), ) @classmethod @@ -56,22 +55,22 @@ class Flags: _analytics_processor: typing.Optional[AnalyticsProcessor] = None @classmethod - def from_feature_state_models( + def from_evaluation_result( cls, - feature_states: typing.Sequence[FeatureStateModel], + evaluation_result: EvaluationResult, analytics_processor: typing.Optional[AnalyticsProcessor], default_flag_handler: typing.Optional[typing.Callable[[str], DefaultFlag]], - identity_id: typing.Optional[typing.Union[str, int]] = None, ) -> Flags: - flags = { - feature_state.feature.name: Flag.from_feature_state_model( - feature_state, identity_id=identity_id - ) - for feature_state in feature_states - } - return cls( - flags=flags, + flags={ + flag["name"]: Flag( + enabled=flag["enabled"], + value=flag["value"], + feature_name=flag["name"], + feature_id=int(flag["feature_key"]), + ) + for flag in evaluation_result["flags"] + }, default_flag_handler=default_flag_handler, _analytics_processor=analytics_processor, ) diff --git a/poetry.lock b/poetry.lock index 506b5b2..a5a43c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -17,14 +17,14 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "certifi" -version = "2024.7.4" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] @@ -42,102 +42,91 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.3" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] [[package]] @@ -243,61 +232,59 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "distlib" -version = "0.3.8" +version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" groups = ["dev"] markers = "python_version >= \"3.9\"" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_version < \"3.11\"" files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.15.4" +version = "3.19.1" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] markers = "python_version >= \"3.9\"" files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8) ; python_version < \"3.11\""] - [[package]] name = "flagsmith-flag-engine" -version = "6.0.2" +version = "6.1.0" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith_flag_engine-6.0.2-py3-none-any.whl", hash = "sha256:dcf104acab328a397c9dae883eefe12bee430033b250995bfcb34a1c5326cde2"}, - {file = "flagsmith_flag_engine-6.0.2.tar.gz", hash = "sha256:0bf82cee6498c2aa2133655e66aba0013babaa0e2503d87123637cc562eab8c9"}, + {file = "flagsmith_flag_engine-6.1.0-py3-none-any.whl", hash = "sha256:0123cb6b4f59fb20edb44f78cbcfe68b3f861f16d15b10e35e289ddb40a0f242"}, + {file = "flagsmith_flag_engine-6.1.0.tar.gz", hash = "sha256:77ac11bdb4d74360f236cc21176fe2c89adec981237b60bba6f82d43fe92ee73"}, ] [package.dependencies] @@ -307,15 +294,15 @@ semver = ">=3.0.1" [[package]] name = "identify" -version = "2.6.0" +version = "2.6.13" description = "File identification library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] markers = "python_version >= \"3.9\"" files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, ] [package.extras] @@ -323,69 +310,78 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" groups = ["main", "dev"] files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "mypy" -version = "1.17.0" +version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] markers = "python_version >= \"3.9\"" files = [ - {file = "mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6"}, - {file = "mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d"}, - {file = "mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b"}, - {file = "mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a"}, - {file = "mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f"}, - {file = "mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937"}, - {file = "mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be"}, - {file = "mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61"}, - {file = "mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f"}, - {file = "mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d"}, - {file = "mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3"}, - {file = "mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70"}, - {file = "mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb"}, - {file = "mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d"}, - {file = "mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8"}, - {file = "mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e"}, - {file = "mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8"}, - {file = "mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d"}, - {file = "mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06"}, - {file = "mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a"}, - {file = "mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889"}, - {file = "mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba"}, - {file = "mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658"}, - {file = "mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c"}, - {file = "mypy-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:63e751f1b5ab51d6f3d219fe3a2fe4523eaa387d854ad06906c63883fde5b1ab"}, - {file = "mypy-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fb09d05e0f1c329a36dcd30e27564a3555717cde87301fae4fb542402ddfad"}, - {file = "mypy-1.17.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b72c34ce05ac3a1361ae2ebb50757fb6e3624032d91488d93544e9f82db0ed6c"}, - {file = "mypy-1.17.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434ad499ad8dde8b2f6391ddfa982f41cb07ccda8e3c67781b1bfd4e5f9450a8"}, - {file = "mypy-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f105f61a5eff52e137fd73bee32958b2add9d9f0a856f17314018646af838e97"}, - {file = "mypy-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:ba06254a5a22729853209550d80f94e28690d5530c661f9416a68ac097b13fc4"}, - {file = "mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496"}, - {file = "mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] [package.dependencies] @@ -429,14 +425,14 @@ files = [ [[package]] name = "packaging" -version = "24.1" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -454,21 +450,21 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] markers = "python_version >= \"3.9\"" files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" @@ -488,15 +484,15 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "4.2.0" +version = "4.3.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" groups = ["dev"] markers = "python_version >= \"3.9\"" files = [ - {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, - {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, + {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, + {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, ] [package.dependencies] @@ -508,26 +504,24 @@ virtualenv = ">=20.10.0" [[package]] name = "pydantic" -version = "2.8.2" +version = "2.10.6" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] +annotated-types = ">=0.6.0" +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-collections" @@ -547,101 +541,112 @@ typing-extensions = ">=4.7.1" [[package]] name = "pydantic-core" -version = "2.20.1" +version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, ] [package.dependencies] @@ -691,14 +696,14 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-mock" -version = "3.14.0" +version = "3.14.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, + {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, + {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, ] [package.dependencies] @@ -794,21 +799,21 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-futures" -version = "1.0.1" +version = "1.0.2" description = "Asynchronous Python HTTP for Humans." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "requests-futures-1.0.1.tar.gz", hash = "sha256:f55a4ef80070e2858e7d1e73123d2bfaeaf25b93fd34384d8ddf148e2b676373"}, - {file = "requests_futures-1.0.1-py2.py3-none-any.whl", hash = "sha256:4a2f5472e9911a79532137d156aa937cd9cd90fec55677f71b2976d1f7a66d38"}, + {file = "requests_futures-1.0.2-py2.py3-none-any.whl", hash = "sha256:a3534af7c2bf670cd7aa730716e9e7d4386497554f87792be7514063b8912897"}, + {file = "requests_futures-1.0.2.tar.gz", hash = "sha256:6b7eb57940336e800faebc3dab506360edec9478f7b22dc570858ad3aa7458da"}, ] [package.dependencies] requests = ">=1.2.0" [package.extras] -dev = ["black (>=22.3.0)", "build (>=0.7.0)", "isort (>=5.11.4)", "pyflakes (>=2.2.0)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "pytest-network (>=0.0.1)", "readme-renderer[rst] (>=26.0)", "twine (>=3.4.2)"] +dev = ["Werkzeug (>=3.0.6)", "black (>=24.3.0,<25.0.0)", "build (>=0.7.0)", "docutils (<=0.20.1)", "greenlet (<=2.0.2) ; python_version < \"3.12\"", "greenlet (>=3.0.0) ; python_version >= \"3.12.0rc0\"", "isort (>=5.11.4)", "pyflakes (>=2.2.0)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "pytest-httpbin (>=2.0.0)", "readme-renderer[rst] (>=26.0)", "twine (>=3.4.2)"] [[package]] name = "responses" @@ -832,14 +837,14 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy [[package]] name = "semver" -version = "3.0.2" +version = "3.0.4" description = "Python helper for Semantic Versioning (https://semver.org)" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, - {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, + {file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}, + {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, ] [[package]] @@ -856,27 +861,57 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] markers = "python_full_version <= \"3.11.0a6\"" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] name = "types-requests" -version = "2.32.0.20240712" +version = "2.32.0.20241016" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, - {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, + {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, + {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, ] [package.dependencies] @@ -884,27 +919,26 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] -markers = {dev = "python_version >= \"3.9\""} [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -915,27 +949,28 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.34.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] markers = "python_version >= \"3.9\"" files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, + {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [metadata] lock-version = "2.1" python-versions = ">=3.8.1,<4" -content-hash = "6d2433e6b79094dc094e2693ae2f5cb08fe78203b11dbb2be7fa5d68416a0efd" +content-hash = "3eee967a4285ab1a4d66d622858fa500032446d372ee1e21c4b8bed2abc9e052" diff --git a/pyproject.toml b/pyproject.toml index 7729285..d14be43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ packages = [{ include = "flagsmith" }] python = ">=3.8.1,<4" requests = "^2.32.3" requests-futures = "^1.0.1" -flagsmith-flag-engine = "^6.0.2" +flagsmith-flag-engine = "^6.1.0" sseclient-py = "^1.8.0" pydantic = "^2" diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 9ad1c84..d037e55 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -90,7 +90,7 @@ def test_get_environment_flags_uses_local_environment_when_available( assert len(all_flags) == 1 assert all_flags[0].feature_name == environment_model.feature_states[0].feature.name assert all_flags[0].enabled == environment_model.feature_states[0].enabled - assert all_flags[0].value == environment_model.feature_states[0].get_value() + assert all_flags[0].value == environment_model.feature_states[0].feature_state_value @responses.activate() @@ -160,18 +160,38 @@ def test_get_identity_flags_uses_local_environment_when_available( feature=FeatureModel(id=1, name="some_feature", type="STANDARD"), enabled=True, featurestate_uuid=str(uuid.uuid4()), + feature_state_value="some-feature-state-value", ) - mock_engine.get_identity_feature_states.return_value = [feature_state] + expected_evaluation_result = { + "flags": [ + { + "name": "some_feature", + "enabled": True, + "value": "some-feature-state-value", + "feature_key": "1", + } + ], + "segments": [], + } + + identifier = "identifier" + traits = {"some_trait": "some_value"} + + mock_engine.get_evaluation_result.return_value = expected_evaluation_result # When - identity_flags = flagsmith.get_identity_flags( - "identifier", traits={"some_trait": "some_value"} - ).all_flags() + identity_flags = flagsmith.get_identity_flags(identifier, traits).all_flags() # Then - mock_engine.get_identity_feature_states.assert_called_once() + mock_engine.get_evaluation_result.assert_called_once() + call_args = mock_engine.get_evaluation_result.call_args + context = call_args[1]["context"] + assert context["identity"]["identifier"] == identifier + assert context["identity"]["traits"]["some_trait"] == "some_value" + assert "some_trait" in context["identity"]["traits"] + assert identity_flags[0].enabled is feature_state.enabled - assert identity_flags[0].value == feature_state.get_value() + assert identity_flags[0].value == feature_state.feature_state_value @responses.activate() diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..b249268 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,126 @@ +import typing + +import pytest +from flag_engine.result.types import EvaluationResult, FlagResult + +from flagsmith.models import Flag, Flags + + +def test_flag_from_evaluation_result() -> None: + # Given + flag_result: FlagResult = { + "name": "test_feature", + "enabled": True, + "value": "test-value", + "feature_key": "123", + } + + # When + flag: Flag = Flag.from_evaluation_result(flag_result) + + # Then + assert flag.enabled is True + assert flag.value == "test-value" + assert flag.feature_name == "test_feature" + assert flag.feature_id == 123 + assert flag.is_default is False + + +@pytest.mark.parametrize( + "flags_result,expected_count,expected_names", + [ + ([], 0, []), + ( + [ + { + "name": "feature1", + "enabled": True, + "value": "value1", + "feature_key": "1", + } + ], + 1, + ["feature1"], + ), + ( + [ + { + "name": "feature1", + "enabled": True, + "value": "value1", + "feature_key": "1", + }, + { + "name": "feature2", + "enabled": False, + "value": None, + "feature_key": "2", + }, + {"name": "feature3", "enabled": True, "value": 42, "feature_key": "3"}, + ], + 3, + ["feature1", "feature2", "feature3"], + ), + ], +) +def test_flags_from_evaluation_result( + flags_result: typing.List[FlagResult], + expected_count: int, + expected_names: typing.List[str], +) -> None: + # Given + evaluation_result: EvaluationResult = { + "flags": flags_result, + "segments": [], + "context": { + "environment": { + "name": "test_environment", + "key": "test_environment_key", + } + }, + } + + # When + flags: Flags = Flags.from_evaluation_result( + evaluation_result=evaluation_result, + analytics_processor=None, + default_flag_handler=None, + ) + + # Then + assert len(flags.flags) == expected_count + + for name in expected_names: + assert name in flags.flags + flag: Flag = flags.flags[name] + assert isinstance(flag, Flag) + assert flag.feature_name == name + + +@pytest.mark.parametrize( + "value,expected", + [ + ("string", "string"), + (42, 42), + (3.14, 3.14), + (True, True), + (False, False), + (None, None), + ], +) +def test_flag_from_evaluation_result_value_types( + value: typing.Any, expected: typing.Any +) -> None: + # Given + flag_result: FlagResult = { + "name": "test_feature", + "enabled": True, + "value": value, + "feature_key": "123", + } + + # When + flag: Flag = Flag.from_evaluation_result(flag_result) + + # Then + assert flag.value == expected From 0372818c9717021c583b561816f54d62eb8be88e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:02:29 +0200 Subject: [PATCH 086/121] ci: pre-commit autoupdate (#137) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f295f10..207279f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: flake8 name: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-yaml - repo: https://github.com/pre-commit/mirrors-prettier From d2f760c472ce40c21c02123cf8871d81b9e4605e Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Thu, 21 Aug 2025 10:07:12 +0100 Subject: [PATCH 087/121] chore(main): release 3.10.1 (#146) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fc62d3d..9037ce1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.10.0" + ".": "3.10.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf41f4..a46b767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [3.10.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.10.0...v3.10.1) (2025-08-21) + +### CI + +- pre-commit autoupdate ([#137](https://github.com/Flagsmith/flagsmith-python-client/issues/137)) + ([0372818](https://github.com/Flagsmith/flagsmith-python-client/commit/0372818c9717021c583b561816f54d62eb8be88e)) + +### Other + +- replacing-deprecated-methods ([#143](https://github.com/Flagsmith/flagsmith-python-client/issues/143)) + ([03715ab](https://github.com/Flagsmith/flagsmith-python-client/commit/03715abc403eeface2bd4d3d472b6da97d7d0e77)) + ## [3.10.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.9.2...v3.10.0) (2025-08-06) ### Features diff --git a/pyproject.toml b/pyproject.toml index d14be43..efbb156 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.10.0" +version = "3.10.1" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From a55a92136699af06390a6570850d45464dcad7aa Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 22 Aug 2025 18:31:57 +0100 Subject: [PATCH 088/121] chore: Add `CODEOWNERS` (#148) --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..1cc6b53 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @flagsmith/flagsmith-back-end From 5ecb0788b6c2903826210e0c453d68769220d250 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Mon, 1 Sep 2025 17:34:34 +0100 Subject: [PATCH 089/121] feat!: Engine V7 compatibility (#150) deps: Bump flagsmith-engine from 6.1.0 to 7.0.0 --- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/pull-request.yml | 16 -- .github/workflows/pytest.yml | 3 +- flagsmith/api/__init__.py | 0 flagsmith/flagsmith.py | 161 ++++++-------- flagsmith/mappers.py | 229 +++++++++++++++++++ flagsmith/offline_handlers.py | 62 ++++-- flagsmith/streaming_manager.py | 12 +- flagsmith/types.py | 11 +- poetry.lock | 342 +++++++++++++---------------- pyproject.toml | 9 +- tests/conftest.py | 14 +- tests/test_analytics.py | 7 +- tests/test_flagsmith.py | 77 ++++--- tests/test_offline_handlers.py | 31 ++- tests/test_streaming_manager.py | 41 ++-- 15 files changed, 603 insertions(+), 412 deletions(-) create mode 100644 flagsmith/api/__init__.py create mode 100644 flagsmith/mappers.py diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 3344120..059f7d0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -25,19 +25,3 @@ jobs: refactor test chore - - name: Auto-label PR with Conventional Commit title - uses: kramen22/conventional-release-labels@v1 - with: - type_labels: | - { - "feat": "feature", - "fix": "fix", - "ci": "ci-cd", - "docs": "docs", - "deps": "dependencies", - "perf": "performance", - "refactor": "refactor", - "test": "testing", - "chore": "chore" - } - ignored_types: '[]' diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 8cc83d3..9699bea 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] steps: - name: Cloning repo @@ -33,7 +33,6 @@ jobs: poetry install --with dev - name: Check for new typing errors - if: ${{ matrix.python-version != '3.8' }} run: poetry run mypy --strict . - name: Run Tests diff --git a/flagsmith/api/__init__.py b/flagsmith/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 71ecf8d..84be0bb 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -1,30 +1,30 @@ import logging import sys import typing -from datetime import timezone +from datetime import datetime +from urllib.parse import urljoin -import pydantic import requests from flag_engine import engine -from flag_engine.context.mappers import map_environment_identity_to_context -from flag_engine.environments.models import EnvironmentModel -from flag_engine.identities.models import IdentityModel -from flag_engine.identities.traits.models import TraitModel -from flag_engine.identities.traits.types import TraitValue from requests.adapters import HTTPAdapter from requests.utils import default_user_agent from urllib3 import Retry from flagsmith.analytics import AnalyticsProcessor from flagsmith.exceptions import FlagsmithAPIError, FlagsmithClientError +from flagsmith.mappers import ( + map_context_and_identity_data_to_context, + map_environment_document_to_context, + map_environment_document_to_environment_updated_at, +) from flagsmith.models import DefaultFlag, Flags, Segment -from flagsmith.offline_handlers import BaseOfflineHandler +from flagsmith.offline_handlers import OfflineHandler from flagsmith.polling_manager import EnvironmentDataPollingManager -from flagsmith.streaming_manager import EventStreamManager, StreamEvent +from flagsmith.streaming_manager import EventStreamManager from flagsmith.types import ( ApplicationMetadata, JsonType, - TraitConfig, + StreamEvent, TraitMapping, ) from flagsmith.utils.identities import generate_identity_data @@ -72,7 +72,7 @@ def __init__( ] = None, proxies: typing.Optional[typing.Dict[str, str]] = None, offline_mode: bool = False, - offline_handler: typing.Optional[BaseOfflineHandler] = None, + offline_handler: typing.Optional[OfflineHandler] = None, enable_realtime_updates: bool = False, application_metadata: typing.Optional[ApplicationMetadata] = None, ): @@ -112,8 +112,8 @@ def __init__( self.default_flag_handler = default_flag_handler self.enable_realtime_updates = enable_realtime_updates self._analytics_processor: typing.Optional[AnalyticsProcessor] = None - self._environment: typing.Optional[EnvironmentModel] = None - self._identity_overrides_by_identifier: typing.Dict[str, IdentityModel] = {} + self._evaluation_context: typing.Optional[engine.EvaluationContext] = None + self._environment_updated_at: typing.Optional[datetime] = None # argument validation if offline_mode and not offline_handler: @@ -129,7 +129,7 @@ def __init__( ) if self.offline_handler: - self._environment = self.offline_handler.get_environment() + self._evaluation_context = self.offline_handler.get_evaluation_context() if not self.offline_mode: if not environment_key: @@ -159,9 +159,9 @@ def __init__( self.request_timeout_seconds = request_timeout_seconds self.session.mount(self.api_url, HTTPAdapter(max_retries=retries)) - self.environment_flags_url = f"{self.api_url}flags/" - self.identities_url = f"{self.api_url}identities/" - self.environment_url = f"{self.api_url}environment-document/" + self.environment_flags_url = urljoin(self.api_url, "flags/") + self.identities_url = urljoin(self.api_url, "identities/") + self.environment_url = urljoin(self.api_url, "environment-document/") if self.enable_local_evaluation: if not environment_key.startswith("ser."): @@ -182,10 +182,13 @@ def _initialise_local_evaluation(self) -> None: # method calls, update the environment manually. self.update_environment() if self.enable_realtime_updates: - if not self._environment: + if not self._evaluation_context: raise ValueError("Unable to get environment from API key") - stream_url = f"{self.realtime_api_url}sse/environments/{self._environment.api_key}/stream" + stream_url = urljoin( + self.realtime_api_url, + f"sse/environments/{self._evaluation_context['environment']['key']}/stream", + ) self.event_stream_thread = EventStreamManager( stream_url=stream_url, @@ -207,15 +210,11 @@ def _initialise_local_evaluation(self) -> None: self.environment_data_polling_manager_thread.start() def handle_stream_event(self, event: StreamEvent) -> None: - if not self._environment: + if not (environment_updated_at := self._environment_updated_at): raise ValueError( - "Unable to access environment. Environment should not be null" + "Cannot handle stream events before retrieving initial environment" ) - environment_updated_at = self._environment.updated_at - if environment_updated_at.tzinfo is None: - environment_updated_at = environment_updated_at.astimezone(timezone.utc) - - if event.updated_at > environment_updated_at: + if event["updated_at"] > environment_updated_at: self.update_environment() def get_environment_flags(self) -> Flags: @@ -224,7 +223,9 @@ def get_environment_flags(self) -> Flags: :return: Flags object holding all the flags for the current environment. """ - if (self.offline_mode or self.enable_local_evaluation) and self._environment: + if ( + self.offline_mode or self.enable_local_evaluation + ) and self._evaluation_context: return self._get_environment_flags_from_document() return self._get_environment_flags_from_api() @@ -250,7 +251,9 @@ def get_identity_flags( :return: Flags object holding all the flags for the given identity. """ traits = traits or {} - if (self.offline_mode or self.enable_local_evaluation) and self._environment: + if ( + self.offline_mode or self.enable_local_evaluation + ) and self._evaluation_context: return self._get_identity_flags_from_document(identifier, traits) return self._get_identity_flags_from_api( identifier, @@ -261,7 +264,7 @@ def get_identity_flags( def get_identity_segments( self, identifier: str, - traits: typing.Optional[typing.Mapping[str, TraitValue]] = None, + traits: typing.Optional[typing.Mapping[str, engine.ContextValue]] = None, ) -> typing.List[Segment]: """ Get a list of segments that the given identity is in. @@ -272,37 +275,44 @@ def get_identity_segments( Flagsmith, e.g. {"num_orders": 10} :return: list of Segment objects that the identity is part of. """ - - if not self._environment: + if not self._evaluation_context: raise FlagsmithClientError( "Local evaluation required to obtain identity segments." ) - traits = traits or {} - identity_model = self._get_identity_model(identifier, **traits) - context = map_environment_identity_to_context( - environment=self._environment, - identity=identity_model, - override_traits=None, + context = map_context_and_identity_data_to_context( + context=self._evaluation_context, + identifier=identifier, + traits=traits, ) + evaluation_result = engine.get_evaluation_result( context=context, ) return [ - Segment(id=int(sm["key"]), name=sm["name"]) - for sm in evaluation_result.get("segments", []) + Segment(id=int(segment_result["key"]), name=segment_result["name"]) + for segment_result in evaluation_result["segments"] ] def update_environment(self) -> None: try: - self._environment = self._get_environment_from_api() - except (FlagsmithAPIError, pydantic.ValidationError): - logger.exception("Error updating environment") + environment_data = self._get_json_response( + self.environment_url, method="GET" + ) + except FlagsmithAPIError: + logger.exception("Error retrieving environment document from API") else: - if overrides := self._environment.identity_overrides: - self._identity_overrides_by_identifier = { - identity.identifier: identity for identity in overrides - } + try: + self._evaluation_context = map_environment_document_to_context( + environment_data, + ) + self._environment_updated_at = ( + map_environment_document_to_environment_updated_at( + environment_data, + ) + ) + except (KeyError, TypeError, ValueError): + logger.exception("Error parsing environment document") def _get_headers( self, @@ -322,22 +332,11 @@ def _get_headers( headers.update(custom_headers or {}) return headers - def _get_environment_from_api(self) -> EnvironmentModel: - environment_data = self._get_json_response(self.environment_url, method="GET") - return EnvironmentModel.model_validate(environment_data) - def _get_environment_flags_from_document(self) -> Flags: - if self._environment is None: + if self._evaluation_context is None: raise TypeError("No environment present") - identity = self._get_identity_model(identifier="", traits=None) - - context = map_environment_identity_to_context( - environment=self._environment, - identity=identity, - override_traits=None, - ) - evaluation_result = engine.get_evaluation_result(context=context) + evaluation_result = engine.get_evaluation_result(self._evaluation_context) return Flags.from_evaluation_result( evaluation_result=evaluation_result, @@ -346,18 +345,18 @@ def _get_environment_flags_from_document(self) -> Flags: ) def _get_identity_flags_from_document( - self, identifier: str, traits: TraitMapping + self, + identifier: str, + traits: TraitMapping, ) -> Flags: - identity_model = self._get_identity_model(identifier, **traits) - if self._environment is None: + if self._evaluation_context is None: raise TypeError("No environment present") - context = map_environment_identity_to_context( - environment=self._environment, - identity=identity_model, - override_traits=None, + context = map_context_and_identity_data_to_context( + context=self._evaluation_context, + identifier=identifier, + traits=traits, ) - evaluation_result = engine.get_evaluation_result( context=context, ) @@ -435,34 +434,6 @@ def _get_json_response( "Unable to get valid response from Flagsmith API." ) from e - def _get_identity_model( - self, - identifier: str, - **traits: typing.Union[TraitValue, TraitConfig], - ) -> IdentityModel: - if not self._environment: - raise FlagsmithClientError( - "Unable to build identity model when no local environment present." - ) - - trait_models = [ - TraitModel( - trait_key=key, - trait_value=value["value"] if isinstance(value, dict) else value, - ) - for key, value in traits.items() - ] - - if identity := self._identity_overrides_by_identifier.get(identifier): - identity.update_traits(trait_models) - return identity - - return IdentityModel( - identifier=identifier, - environment_api_key=self._environment.api_key, - identity_traits=trait_models, - ) - def __del__(self) -> None: if hasattr(self, "environment_data_polling_manager_thread"): self.environment_data_polling_manager_thread.stop() diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py new file mode 100644 index 0000000..0e8fd04 --- /dev/null +++ b/flagsmith/mappers.py @@ -0,0 +1,229 @@ +import json +import typing +from collections import defaultdict +from datetime import datetime, timezone +from operator import itemgetter + +import sseclient +from flag_engine.context.types import ( + EvaluationContext, + FeatureContext, + SegmentContext, + SegmentRule, +) +from flag_engine.segments.types import ContextValue + +from flagsmith.types import StreamEvent, TraitConfig + +OverrideKey = typing.Tuple[ + str, + str, + bool, + typing.Any, +] +OverridesKey = typing.Tuple[OverrideKey, ...] + + +def map_sse_event_to_stream_event(event: sseclient.Event) -> StreamEvent: + event_data = json.loads(event.data) + return { + "updated_at": datetime.fromtimestamp( + event_data["updated_at"], + tz=timezone.utc, + ) + } + + +def map_environment_document_to_environment_updated_at( + environment_document: dict[str, typing.Any], +) -> datetime: + if ( + updated_at := datetime.fromisoformat(environment_document["updated_at"]) + ).tzinfo is None: + return updated_at.replace(tzinfo=timezone.utc) + return updated_at.astimezone(tz=timezone.utc) + + +def map_context_and_identity_data_to_context( + context: EvaluationContext, + identifier: str, + traits: typing.Optional[ + typing.Mapping[ + str, + typing.Union[ + ContextValue, + TraitConfig, + ], + ] + ], +) -> EvaluationContext: + return { + **context, + "identity": { + "identifier": identifier, + "key": f"{context['environment']['key']}_{identifier}", + "traits": { + trait_key: ( + trait_value_or_config["value"] + if isinstance(trait_value_or_config, dict) + else trait_value_or_config + ) + for trait_key, trait_value_or_config in (traits or {}).items() + }, + }, + } + + +def map_environment_document_to_context( + environment_document: dict[str, typing.Any], +) -> EvaluationContext: + return { + "environment": { + "key": environment_document["api_key"], + "name": "Test Environment", + }, + "features": { + feature["name"]: feature + for feature in _map_environment_document_feature_states_to_feature_contexts( + environment_document["feature_states"] + ) + }, + "segments": { + **{ + (segment_key := str(segment["id"])): { + "key": segment_key, + "name": segment["name"], + "rules": _map_environment_document_rules_to_context_rules( + segment["rules"] + ), + "overrides": list( + _map_environment_document_feature_states_to_feature_contexts( + segment.get("feature_states") or [] + ) + ), + } + for segment in environment_document["project"]["segments"] + }, + **_map_identity_overrides_to_segments( + environment_document.get("identity_overrides") or [] + ), + }, + } + + +def _map_identity_overrides_to_segments( + identity_overrides: list[dict[str, typing.Any]], +) -> dict[str, SegmentContext]: + features_to_identifiers: typing.Dict[ + OverridesKey, + typing.List[str], + ] = defaultdict(list) + for identity_override in identity_overrides: + identity_features: list[dict[str, typing.Any]] = identity_override[ + "identity_features" + ] + if not identity_features: + continue + overrides_key = tuple( + ( + str(feature_state["feature"]["id"]), + feature_state["feature"]["name"], + feature_state["enabled"], + feature_state["feature_state_value"], + ) + for feature_state in sorted( + identity_features, + key=lambda feature_state: feature_state["feature"]["name"], + ) + ) + features_to_identifiers[overrides_key].append(identity_override["identifier"]) + segment_contexts: typing.Dict[str, SegmentContext] = {} + for overrides_key, identifiers in features_to_identifiers.items(): + # Create a segment context for each unique set of overrides + # Generate a unique key to avoid collisions + segment_key = str(hash(overrides_key)) + segment_contexts[segment_key] = SegmentContext( + key="", # Identity override segments never use % Split operator + name="identity_overrides", + rules=[ + { + "type": "ALL", + "conditions": [ + { + "property": "$.identity.identifier", + "operator": "IN", + "value": identifiers, + } + ], + } + ], + overrides=[ + { + "key": "", # Identity overrides never carry multivariate options + "feature_key": feature_key, + "name": feature_name, + "enabled": feature_enabled, + "value": feature_value, + "priority": float("-inf"), # Highest possible priority + } + for feature_key, feature_name, feature_enabled, feature_value in overrides_key + ], + ) + return segment_contexts + + +def _map_environment_document_rules_to_context_rules( + rules: list[dict[str, typing.Any]], +) -> list[SegmentRule]: + return [ + dict( + type=rule["type"], + conditions=[ + dict( + property=condition.get("property_"), + operator=condition["operator"], + value=condition["value"], + ) + for condition in rule.get("conditions", []) + ], + rules=_map_environment_document_rules_to_context_rules( + rule.get("rules", []) + ), + ) + for rule in rules + ] + + +def _map_environment_document_feature_states_to_feature_contexts( + feature_states: list[dict[str, typing.Any]], +) -> typing.Iterable[FeatureContext]: + for feature_state in feature_states: + feature_context = FeatureContext( + key=str(feature_state["id"]), + feature_key=str(feature_state["feature"]["id"]), + name=feature_state["feature"]["name"], + enabled=feature_state["enabled"], + value=feature_state["feature_state_value"], + ) + if multivariate_feature_state_values := feature_state.get( + "multivariate_feature_state_values" + ): + feature_context["variants"] = [ + { + "value": multivariate_feature_state_value[ + "multivariate_feature_option" + ]["value"], + "weight": multivariate_feature_state_value["percentage_allocation"], + } + for multivariate_feature_state_value in sorted( + multivariate_feature_state_values, + key=itemgetter("id"), + ) + ] + if ( + priority := (feature_state.get("feature_segment") or {}).get("priority") + is not None + ): + feature_context["priority"] = priority + + yield feature_context diff --git a/flagsmith/offline_handlers.py b/flagsmith/offline_handlers.py index bde1ad0..3248339 100644 --- a/flagsmith/offline_handlers.py +++ b/flagsmith/offline_handlers.py @@ -1,20 +1,56 @@ -from abc import ABC, abstractmethod +import json +from pathlib import Path +from typing import Protocol -from flag_engine.environments.models import EnvironmentModel +from flag_engine.context.types import EvaluationContext +from flagsmith.mappers import map_environment_document_to_context -class BaseOfflineHandler(ABC): - @abstractmethod - def get_environment(self) -> EnvironmentModel: - raise NotImplementedError() +class OfflineHandler(Protocol): + def get_evaluation_context(self) -> EvaluationContext: ... -class LocalFileHandler(BaseOfflineHandler): - def __init__(self, environment_document_path: str): - with open(environment_document_path) as environment_document: - self.environment = EnvironmentModel.model_validate_json( - environment_document.read() + +class EvaluationContextLocalFileHandler: + """ + Handler to load evaluation context from a local JSON file. + The JSON file should contain the full evaluation context as per Flagsmith Engine's specification. + + JSON schema: + https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-context.json + """ + + def __init__(self, file_path: str) -> None: + self.evaluation_context: EvaluationContext = json.loads( + Path(file_path).read_text(), + ) + + def get_evaluation_context(self) -> EvaluationContext: + return self.evaluation_context + + +class EnvironmentDocumentLocalFileHandler: + """ + Handler to load evaluation context from a local JSON file containing the environment document. + The JSON file should contain the environment document as returned by the Flagsmith API. + + API documentation: + https://api.flagsmith.com/api/v1/docs/#/api/api_v1_environment-document_list + """ + + def __init__(self, file_path: str) -> None: + self.evaluation_context: EvaluationContext = ( + map_environment_document_to_context( + json.loads( + Path(file_path).read_text(), + ), ) + ) + + def get_evaluation_context(self) -> EvaluationContext: + return self.evaluation_context + - def get_environment(self) -> EnvironmentModel: - return self.environment +# For backward compatibility, use the old class name for +# the local file handler implementation dependant on the environment document. +LocalFileHandler = EnvironmentDocumentLocalFileHandler diff --git a/flagsmith/streaming_manager.py b/flagsmith/streaming_manager.py index d299fe8..c4f2c6c 100644 --- a/flagsmith/streaming_manager.py +++ b/flagsmith/streaming_manager.py @@ -3,15 +3,13 @@ import typing from typing import Callable, Optional -import pydantic import requests import sseclient -logger = logging.getLogger(__name__) - +from flagsmith.mappers import map_sse_event_to_stream_event +from flagsmith.types import StreamEvent -class StreamEvent(pydantic.BaseModel): - updated_at: pydantic.AwareDatetime +logger = logging.getLogger(__name__) class EventStreamManager(threading.Thread): @@ -40,9 +38,9 @@ def run(self) -> None: ) as response: sse_client = sseclient.SSEClient(chunk for chunk in response) for event in sse_client.events(): - self.on_event(StreamEvent.model_validate_json(event.data)) + self.on_event(map_sse_event_to_stream_event(event)) - except (requests.RequestException, pydantic.ValidationError): + except (requests.RequestException, ValueError, TypeError): logger.exception("Error opening or reading from the event stream") def stop(self) -> None: diff --git a/flagsmith/types.py b/flagsmith/types.py index c0d535a..d5c4b0f 100644 --- a/flagsmith/types.py +++ b/flagsmith/types.py @@ -1,6 +1,7 @@ import typing +from datetime import datetime -from flag_engine.identities.traits.types import TraitValue +from flag_engine.engine import ContextValue from typing_extensions import NotRequired, TypeAlias _JsonScalarType: TypeAlias = typing.Union[ @@ -17,12 +18,16 @@ ] +class StreamEvent(typing.TypedDict): + updated_at: datetime + + class TraitConfig(typing.TypedDict): - value: TraitValue + value: ContextValue transient: bool -TraitMapping: TypeAlias = typing.Mapping[str, typing.Union[TraitValue, TraitConfig]] +TraitMapping: TypeAlias = typing.Mapping[str, typing.Union[ContextValue, TraitConfig]] class ApplicationMetadata(typing.TypedDict): diff --git a/poetry.lock b/poetry.lock index a5a43c9..a90c934 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,20 +1,5 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "certifi" version = "2025.8.3" @@ -34,7 +19,6 @@ description = "Validate configuration and produce human readable error messages. optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -237,7 +221,6 @@ description = "Distribution utilities" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -269,7 +252,6 @@ description = "A platform independent file lock." optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -277,20 +259,20 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "6.1.0" +version = "7.0.0" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith_flag_engine-6.1.0-py3-none-any.whl", hash = "sha256:0123cb6b4f59fb20edb44f78cbcfe68b3f861f16d15b10e35e289ddb40a0f242"}, - {file = "flagsmith_flag_engine-6.1.0.tar.gz", hash = "sha256:77ac11bdb4d74360f236cc21176fe2c89adec981237b60bba6f82d43fe92ee73"}, + {file = "flagsmith_flag_engine-7.0.0-py3-none-any.whl", hash = "sha256:59c1f13eb920de7182e1992bf4504b7cbdf991fe2391ce42dd8cdda55554ab3a"}, + {file = "flagsmith_flag_engine-7.0.0.tar.gz", hash = "sha256:7db6e6515786a949ceb9036c1b0edcd5cb441aff3d083b4a9648bfd035eab9b5"}, ] [package.dependencies] -pydantic = ">=2.3.0,<3" -pydantic-collections = ">=0.5.1,<1" -semver = ">=3.0.1" +jsonpath-rfc9535 = ">=0.1.5,<1" +semver = ">=3.0.4,<4" +typing-extensions = ">=4.14.1,<5" [[package]] name = "identify" @@ -299,7 +281,6 @@ description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, @@ -335,6 +316,47 @@ files = [ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] +[[package]] +name = "iregexp-check" +version = "0.1.4" +description = "" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "iregexp_check-0.1.4-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:385a90d450706b9f934b5c82137247e24423c990d250da55630a792ccb7e2974"}, + {file = "iregexp_check-0.1.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:003434c2d7e13ea91e2ff1d5038f87641e9dc44513a0544e3c29e91dfb21b871"}, + {file = "iregexp_check-0.1.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9cbb6fe0aaae0c7b9b8d4ba05a6d8283cf747dbd06b8e0442f05e87c9d5e1c"}, + {file = "iregexp_check-0.1.4-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef58d44e4ae9aaca89be2898e416e6e168aff62cd5b1820d531fa855ee8e2fb1"}, + {file = "iregexp_check-0.1.4-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a473fb428b55031f64db1e52447d5bffd6bba2f3b760052592a44951cbddd8ab"}, + {file = "iregexp_check-0.1.4-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ce621946fc42e0d9f9475bf1360d91e281f84c199cbac2de24973e55bcdc92"}, + {file = "iregexp_check-0.1.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c670722da7283ed15d1401eca684628248491bb612e54a41bc60f86d32b67a5"}, + {file = "iregexp_check-0.1.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8e8bb2dc1f08110dde37ae52a42f2487365178f43625995579d6cca4ec9f683"}, + {file = "iregexp_check-0.1.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7beffdc3179334e18975a64399915922842880b8960dc4b04903f9b1ffdad35a"}, + {file = "iregexp_check-0.1.4-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:01b374e01719d9e2a1ad141aed5e5d34acf71e156db269b578a46570d32708af"}, + {file = "iregexp_check-0.1.4-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5a7b1340c34cc8c93b80716b75f7faaec3a8662631b1c33a249d68e78d8fdab2"}, + {file = "iregexp_check-0.1.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2dc5a74d3190e0ecd7e30a5394ed6086962ebe644f21d440954ec2d11de691f0"}, + {file = "iregexp_check-0.1.4-cp38-abi3-win32.whl", hash = "sha256:b24ef4264546a899e1e3407d111024d02af42f7b8575250dc4d9fc79011e2a5c"}, + {file = "iregexp_check-0.1.4-cp38-abi3-win_amd64.whl", hash = "sha256:50837bbe9b09abdb7b387d9c7dc2eda470a77e8b29ac315a0e1409b147db14bd"}, + {file = "iregexp_check-0.1.4.tar.gz", hash = "sha256:a98e77dd2d9fc91db04f8d9f295f3d69e402813bac5413f22e5866958a902bc1"}, +] + +[[package]] +name = "jsonpath-rfc9535" +version = "0.1.6" +description = "RFC 9535 - JSONPath: Query Expressions for JSON in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jsonpath_rfc9535-0.1.6-py3-none-any.whl", hash = "sha256:dc569660a3a44c8d1d111b6fe86725680914303bedfc97fde3c15f56d89553cb"}, + {file = "jsonpath_rfc9535-0.1.6.tar.gz", hash = "sha256:0d74848f6a7a7335489648bc3c02e61ce52a527424b252e55edc12ec50305c02"}, +] + +[package.dependencies] +iregexp-check = ">=0.1.4" +regex = "*" + [[package]] name = "mypy" version = "1.17.1" @@ -342,7 +364,6 @@ description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, @@ -404,7 +425,6 @@ description = "Type system extensions for programs checked with the mypy type ch optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -417,7 +437,6 @@ description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -442,7 +461,6 @@ description = "Utility library for gitignore style pattern matching of file path optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -455,7 +473,6 @@ description = "A small Python package for determining appropriate platform-speci optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -489,7 +506,6 @@ description = "A framework for managing and maintaining multi-language pre-commi optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, @@ -503,155 +519,17 @@ pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] -name = "pydantic" -version = "2.10.6" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, - {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, -] - -[package.dependencies] -annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" -typing-extensions = ">=4.12.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] - -[[package]] -name = "pydantic-collections" -version = "0.6.0" -description = "Collections of pydantic models" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pydantic_collections-0.6.0-py3-none-any.whl", hash = "sha256:ec559722abf6a0f80e6f00b3d28f0f39c0ed5feb1641166230eb75e9da880162"}, - {file = "pydantic_collections-0.6.0.tar.gz", hash = "sha256:c34d3fd1df5600b315cdecdd8e74eacd4c8c607b7e3f2c9392b2a15850a4ef9e"}, -] - -[package.dependencies] -pydantic = ">=1.8.2,<3.0" -typing-extensions = ">=4.7.1" - -[[package]] -name = "pydantic-core" -version = "2.27.2" -description = "Core functionality for Pydantic validation and serialization" +name = "pyfakefs" +version = "5.9.2" +description = "Implements a fake file system that mocks the Python file system modules." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, - {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, - {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, - {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, - {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, - {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, - {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, - {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, - {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, - {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, - {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, - {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, - {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, - {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, - {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, - {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, - {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, - {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, - {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, - {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, - {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, - {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, - {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, - {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, - {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, - {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, + {file = "pyfakefs-5.9.2-py3-none-any.whl", hash = "sha256:8fcea36051d5b8827d49f5e2d242e91a51c5af6b78eeffa8e7adb899a6763e99"}, + {file = "pyfakefs-5.9.2.tar.gz", hash = "sha256:66c5c6ccd4097b484f8782f9a5078fee0533d465e0d9caf594c9157d54382553"}, ] -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - [[package]] name = "pytest" version = "7.4.4" @@ -775,6 +653,103 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "regex" +version = "2025.7.34" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95b4639c77d414efa93c8de14ce3f7965a94d007e068a94f9d4997bb9bd9c81f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7de1ceed5a5f84f342ba4a9f4ae589524adf9744b2ee61b5da884b5b659834"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02e5860a250cd350c4933cf376c3bc9cb28948e2c96a8bc042aee7b985cfa26f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a5966220b9a1a88691282b7e4350e9599cf65780ca60d914a798cb791aa1177"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48fb045bbd4aab2418dc1ba2088a5e32de4bfe64e1457b948bb328a8dc2f1c2e"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20ff8433fa45e131f7316594efe24d4679c5449c0ca69d91c2f9d21846fdf064"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c436fd1e95c04c19039668cfb548450a37c13f051e8659f40aed426e36b3765f"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b85241d3cfb9f8a13cefdfbd58a2843f208f2ed2c88181bf84e22e0c7fc066d"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:075641c94126b064c65ab86e7e71fc3d63e7ff1bea1fb794f0773c97cdad3a03"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70645cad3407d103d1dbcb4841839d2946f7d36cf38acbd40120fee1682151e5"}, + {file = "regex-2025.7.34-cp310-cp310-win32.whl", hash = "sha256:3b836eb4a95526b263c2a3359308600bd95ce7848ebd3c29af0c37c4f9627cd3"}, + {file = "regex-2025.7.34-cp310-cp310-win_amd64.whl", hash = "sha256:cbfaa401d77334613cf434f723c7e8ba585df162be76474bccc53ae4e5520b3a"}, + {file = "regex-2025.7.34-cp310-cp310-win_arm64.whl", hash = "sha256:bca11d3c38a47c621769433c47f364b44e8043e0de8e482c5968b20ab90a3986"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da304313761b8500b8e175eb2040c4394a875837d5635f6256d6fa0377ad32c8"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:35e43ebf5b18cd751ea81455b19acfdec402e82fe0dc6143edfae4c5c4b3909a"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96bbae4c616726f4661fe7bcad5952e10d25d3c51ddc388189d8864fbc1b3c68"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9feab78a1ffa4f2b1e27b1bcdaad36f48c2fed4870264ce32f52a393db093c78"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f14b36e6d4d07f1a5060f28ef3b3561c5d95eb0651741474ce4c0a4c56ba8719"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85c3a958ef8b3d5079c763477e1f09e89d13ad22198a37e9d7b26b4b17438b33"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37555e4ae0b93358fa7c2d240a4291d4a4227cc7c607d8f85596cdb08ec0a083"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee38926f31f1aa61b0232a3a11b83461f7807661c062df9eb88769d86e6195c3"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a664291c31cae9c4a30589bd8bc2ebb56ef880c9c6264cb7643633831e606a4d"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f3e5c1e0925e77ec46ddc736b756a6da50d4df4ee3f69536ffb2373460e2dafd"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d428fc7731dcbb4e2ffe43aeb8f90775ad155e7db4347a639768bc6cd2df881a"}, + {file = "regex-2025.7.34-cp311-cp311-win32.whl", hash = "sha256:e154a7ee7fa18333ad90b20e16ef84daaeac61877c8ef942ec8dfa50dc38b7a1"}, + {file = "regex-2025.7.34-cp311-cp311-win_amd64.whl", hash = "sha256:24257953d5c1d6d3c129ab03414c07fc1a47833c9165d49b954190b2b7f21a1a"}, + {file = "regex-2025.7.34-cp311-cp311-win_arm64.whl", hash = "sha256:3157aa512b9e606586900888cd469a444f9b898ecb7f8931996cb715f77477f0"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282"}, + {file = "regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588"}, + {file = "regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62"}, + {file = "regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0"}, + {file = "regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1"}, + {file = "regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997"}, + {file = "regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e"}, + {file = "regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb"}, + {file = "regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae"}, + {file = "regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd5edc3f453de727af267c7909d083e19f6426fc9dd149e332b6034f2a5611e6"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa1cdfb8db96ef20137de5587954c812821966c3e8b48ffc871e22d7ec0a4938"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89c9504fc96268e8e74b0283e548f53a80c421182a2007e3365805b74ceef936"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33be70d75fa05a904ee0dc43b650844e067d14c849df7e82ad673541cd465b5f"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57d25b6732ea93eeb1d090e8399b6235ca84a651b52d52d272ed37d3d2efa0f1"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:baf2fe122a3db1c0b9f161aa44463d8f7e33eeeda47bb0309923deb743a18276"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a764a83128af9c1a54be81485b34dca488cbcacefe1e1d543ef11fbace191e1"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7f663ccc4093877f55b51477522abd7299a14c5bb7626c5238599db6a0cb95d"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4913f52fbc7a744aaebf53acd8d3dc1b519e46ba481d4d7596de3c862e011ada"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:efac4db9e044d47fd3b6b0d40b6708f4dfa2d8131a5ac1d604064147c0f552fd"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7373afae7cfb716e3b8e15d0184510d518f9d21471f2d62918dbece85f2c588f"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9960d162f3fecf6af252534a1ae337e9c2e20d74469fed782903b24e2cc9d3d7"}, + {file = "regex-2025.7.34-cp39-cp39-win32.whl", hash = "sha256:95d538b10eb4621350a54bf14600cc80b514211d91a019dc74b8e23d2159ace5"}, + {file = "regex-2025.7.34-cp39-cp39-win_amd64.whl", hash = "sha256:f7f3071b5faa605b0ea51ec4bb3ea7257277446b053f4fd3ad02b1dcb4e64353"}, + {file = "regex-2025.7.34-cp39-cp39-win_arm64.whl", hash = "sha256:716a47515ba1d03f8e8a61c5013041c8c90f2e21f055203498105d7571b44531"}, + {file = "regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a"}, +] + [[package]] name = "requests" version = "2.32.4" @@ -919,14 +894,14 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.13.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, - {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [[package]] @@ -954,7 +929,6 @@ description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version >= \"3.9\"" files = [ {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, @@ -972,5 +946,5 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" -python-versions = ">=3.8.1,<4" -content-hash = "3eee967a4285ab1a4d66d622858fa500032446d372ee1e21c4b8bed2abc9e052" +python-versions = ">=3.9,<4" +content-hash = "3738927be08947fe27e40be874c3c1429dc98ce072f84468c205e208670bf724" diff --git a/pyproject.toml b/pyproject.toml index efbb156..9099882 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,12 +10,11 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -python = ">=3.8.1,<4" +python = ">=3.9,<4" requests = "^2.32.3" requests-futures = "^1.0.1" -flagsmith-flag-engine = "^6.1.0" +flagsmith-flag-engine = "^7.0.0" sseclient-py = "^1.8.0" -pydantic = "^2" [tool.poetry.group.dev] optional = true @@ -28,13 +27,13 @@ pytest-mock = "^3.6.1" pre-commit = { version = "^4.2.0", python = ">=3.9,<4" } responses = "^0.24.1" types-requests = "^2.32" +pyfakefs = "^5.9.2" [tool.mypy] -plugins = ["pydantic.mypy"] exclude = ["example/*"] [tool.black] -target-version = ["py38"] +target-version = ["py39"] [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/conftest.py b/tests/conftest.py index 0d28bf2..38b98d9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,11 +7,13 @@ import pytest import responses -from flag_engine.environments.models import EnvironmentModel +from flag_engine.engine import EvaluationContext +from pyfakefs.fake_filesystem import FakeFilesystem from pytest_mock import MockerFixture from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor +from flagsmith.mappers import map_environment_document_to_context DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -39,8 +41,10 @@ def flagsmith(api_key: str) -> Flagsmith: @pytest.fixture() -def environment_json() -> typing.Generator[str, None, None]: - with open(os.path.join(DATA_DIR, "environment.json"), "rt") as f: +def environment_json(fs: FakeFilesystem) -> typing.Generator[str, None, None]: + environment_json_path = os.path.join(DATA_DIR, "environment.json") + fs.add_real_file(environment_json_path) + with open(environment_json_path, "rt") as f: yield f.read() @@ -70,8 +74,8 @@ def local_eval_flagsmith( @pytest.fixture() -def environment_model(environment_json: str) -> EnvironmentModel: - return EnvironmentModel.model_validate_json(environment_json) +def evaluation_context(environment_json: str) -> EvaluationContext: + return map_environment_document_to_context(json.loads(environment_json)) @pytest.fixture() diff --git a/tests/test_analytics.py b/tests/test_analytics.py index 7a2fd65..daaf420 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -53,9 +53,10 @@ def test_analytics_processor_calling_track_feature_calls_flush_when_timer_runs_o analytics_processor: AnalyticsProcessor, ) -> None: # Given - with mock.patch("flagsmith.analytics.datetime") as mocked_datetime, mock.patch( - "flagsmith.analytics.session" - ) as session: + with ( + mock.patch("flagsmith.analytics.datetime") as mocked_datetime, + mock.patch("flagsmith.analytics.session") as session, + ): # Let's move the time mocked_datetime.now.return_value = datetime.now() + timedelta( seconds=ANALYTICS_TIMER + 1 diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index d037e55..f87b670 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -2,13 +2,11 @@ import sys import time import typing -import uuid import pytest import requests import responses -from flag_engine.environments.models import EnvironmentModel -from flag_engine.features.models import FeatureModel, FeatureStateModel +from flag_engine.engine import EvaluationContext from pytest_mock import MockerFixture from responses import matchers @@ -18,7 +16,7 @@ FlagsmithFeatureDoesNotExistError, ) from flagsmith.models import DefaultFlag, Flags -from flagsmith.offline_handlers import BaseOfflineHandler +from flagsmith.offline_handlers import OfflineHandler def test_flagsmith_starts_polling_manager_on_init_if_enabled( @@ -40,18 +38,20 @@ def test_flagsmith_starts_polling_manager_on_init_if_enabled( @responses.activate() def test_update_environment_sets_environment( - flagsmith: Flagsmith, environment_json: str, environment_model: EnvironmentModel + flagsmith: Flagsmith, + environment_json: str, + evaluation_context: EvaluationContext, ) -> None: # Given responses.add(method="GET", url=flagsmith.environment_url, body=environment_json) - assert flagsmith._environment is None + assert flagsmith._evaluation_context is None # When flagsmith.update_environment() # Then - assert flagsmith._environment is not None - assert flagsmith._environment == environment_model + assert flagsmith._evaluation_context is not None + assert flagsmith._evaluation_context == evaluation_context @responses.activate() @@ -76,10 +76,11 @@ def test_get_environment_flags_calls_api_when_no_local_environment( @responses.activate() def test_get_environment_flags_uses_local_environment_when_available( - flagsmith: Flagsmith, environment_model: EnvironmentModel + flagsmith: Flagsmith, + evaluation_context: EvaluationContext, ) -> None: # Given - flagsmith._environment = environment_model + flagsmith._evaluation_context = evaluation_context flagsmith.enable_local_evaluation = True # When @@ -88,9 +89,9 @@ def test_get_environment_flags_uses_local_environment_when_available( # Then assert len(responses.calls) == 0 assert len(all_flags) == 1 - assert all_flags[0].feature_name == environment_model.feature_states[0].feature.name - assert all_flags[0].enabled == environment_model.feature_states[0].enabled - assert all_flags[0].value == environment_model.feature_states[0].feature_state_value + assert all_flags[0].feature_name == "some_feature" + assert all_flags[0].enabled is True + assert all_flags[0].value == "some-value" @responses.activate() @@ -149,19 +150,15 @@ def test_get_identity_flags_calls_api_when_no_local_environment_with_traits( @responses.activate() def test_get_identity_flags_uses_local_environment_when_available( - flagsmith: Flagsmith, environment_model: EnvironmentModel, mocker: MockerFixture + flagsmith: Flagsmith, + evaluation_context: EvaluationContext, + mocker: MockerFixture, ) -> None: # Given - flagsmith._environment = environment_model + flagsmith._evaluation_context = evaluation_context flagsmith.enable_local_evaluation = True mock_engine = mocker.patch("flagsmith.flagsmith.engine") - feature_state = FeatureStateModel( - feature=FeatureModel(id=1, name="some_feature", type="STANDARD"), - enabled=True, - featurestate_uuid=str(uuid.uuid4()), - feature_state_value="some-feature-state-value", - ) expected_evaluation_result = { "flags": [ { @@ -190,8 +187,8 @@ def test_get_identity_flags_uses_local_environment_when_available( assert context["identity"]["traits"]["some_trait"] == "some_value" assert "some_trait" in context["identity"]["traits"] - assert identity_flags[0].enabled is feature_state.enabled - assert identity_flags[0].value == feature_state.feature_state_value + assert identity_flags[0].enabled is True + assert identity_flags[0].value == "some-feature-state-value" @responses.activate() @@ -229,7 +226,6 @@ def test_get_identity_flags__transient_identity__calls_expected( def test_get_identity_flags__transient_trait_keys__calls_expected( flagsmith: Flagsmith, identities_json: str, - environment_model: EnvironmentModel, mocker: MockerFixture, ) -> None: # Given @@ -487,7 +483,7 @@ def default_flag_handler(feature_name: str) -> DefaultFlag: def test_get_identity_segments_no_traits( - local_eval_flagsmith: Flagsmith, environment_model: EnvironmentModel + local_eval_flagsmith: Flagsmith, ) -> None: # Given identifier = "identifier" @@ -500,7 +496,7 @@ def test_get_identity_segments_no_traits( def test_get_identity_segments_with_valid_trait( - local_eval_flagsmith: Flagsmith, environment_model: EnvironmentModel + local_eval_flagsmith: Flagsmith, ) -> None: # Given identifier = "identifier" @@ -530,11 +526,11 @@ def test_initialise_flagsmith_with_proxies() -> None: assert flagsmith.session.proxies == proxies -def test_offline_mode(environment_model: EnvironmentModel) -> None: +def test_offline_mode(evaluation_context: EvaluationContext) -> None: # Given - class DummyOfflineHandler(BaseOfflineHandler): - def get_environment(self) -> EnvironmentModel: - return environment_model + class DummyOfflineHandler: + def get_evaluation_context(self) -> EvaluationContext: + return evaluation_context # When flagsmith = Flagsmith(offline_mode=True, offline_handler=DummyOfflineHandler()) @@ -550,12 +546,13 @@ def get_environment(self) -> EnvironmentModel: @responses.activate() def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( - mocker: MockerFixture, environment_model: EnvironmentModel + mocker: MockerFixture, + evaluation_context: EvaluationContext, ) -> None: # Given api_url = "http://some.flagsmith.com/api/v1/" - mock_offline_handler = mocker.MagicMock(spec=BaseOfflineHandler) - mock_offline_handler.get_environment.return_value = environment_model + mock_offline_handler = mocker.MagicMock(spec=OfflineHandler) + mock_offline_handler.get_evaluation_context.return_value = evaluation_context flagsmith = Flagsmith( environment_key="some-key", @@ -571,7 +568,7 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( identity_flags = flagsmith.get_identity_flags("identity", traits={}) # Then - mock_offline_handler.get_environment.assert_called_once_with() + mock_offline_handler.get_evaluation_context.assert_called_once_with() assert environment_flags.is_feature_enabled("some_feature") is True assert environment_flags.get_feature_value("some_feature") == "some-value" @@ -583,13 +580,13 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( @responses.activate() def test_offline_mode__local_evaluation__correct_fallback( mocker: MockerFixture, - environment_model: EnvironmentModel, + evaluation_context: EvaluationContext, caplog: pytest.LogCaptureFixture, ) -> None: # Given api_url = "http://some.flagsmith.com/api/v1/" - mock_offline_handler = mocker.MagicMock(spec=BaseOfflineHandler) - mock_offline_handler.get_environment.return_value = environment_model + mock_offline_handler = mocker.MagicMock(spec=OfflineHandler) + mock_offline_handler.get_evaluation_context.return_value = evaluation_context mocker.patch("flagsmith.flagsmith.EnvironmentDataPollingManager") @@ -607,7 +604,7 @@ def test_offline_mode__local_evaluation__correct_fallback( identity_flags = flagsmith.get_identity_flags("identity", traits={}) # Then - mock_offline_handler.get_environment.assert_called_once_with() + mock_offline_handler.get_evaluation_context.assert_called_once_with() assert environment_flags.is_feature_enabled("some_feature") is True assert environment_flags.get_feature_value("some_feature") == "some-value" @@ -617,7 +614,7 @@ def test_offline_mode__local_evaluation__correct_fallback( [error_log_record] = caplog.records assert error_log_record.levelname == "ERROR" - assert error_log_record.message == "Error updating environment" + assert error_log_record.message == "Error retrieving environment document from API" def test_cannot_use_offline_mode_without_offline_handler() -> None: @@ -636,7 +633,7 @@ def test_cannot_use_default_handler_and_offline_handler(mocker: MockerFixture) - # When with pytest.raises(ValueError) as e: Flagsmith( - offline_handler=mocker.MagicMock(spec=BaseOfflineHandler), + offline_handler=mocker.MagicMock(spec=OfflineHandler), default_flag_handler=lambda flag_name: DefaultFlag( enabled=True, value="foo" ), diff --git a/tests/test_offline_handlers.py b/tests/test_offline_handlers.py index b3cc597..4d77ff3 100644 --- a/tests/test_offline_handlers.py +++ b/tests/test_offline_handlers.py @@ -1,22 +1,21 @@ -from unittest.mock import mock_open, patch - -from flag_engine.environments.models import EnvironmentModel +from flag_engine.engine import EvaluationContext +from pyfakefs.fake_filesystem import FakeFilesystem from flagsmith.offline_handlers import LocalFileHandler -def test_local_file_handler(environment_json: str) -> None: - with patch("builtins.open", mock_open(read_data=environment_json)) as mock_file: - # Given - environment_document_file_path = "/some/path/environment.json" - local_file_handler = LocalFileHandler(environment_document_file_path) +def test_local_file_handler( + fs: FakeFilesystem, + evaluation_context: EvaluationContext, + environment_json: str, +) -> None: + # Given + environment_document_file_path = "/some/path/environment.json" + fs.create_file(environment_document_file_path, contents=environment_json) + local_file_handler = LocalFileHandler(environment_document_file_path) - # When - environment_model = local_file_handler.get_environment() + # When + result = local_file_handler.get_evaluation_context() - # Then - assert isinstance(environment_model, EnvironmentModel) - assert ( - environment_model.api_key == "B62qaMZNwfiqT76p38ggrQ" - ) # hard coded from json file - mock_file.assert_called_once_with(environment_document_file_path) + # Then + assert result == evaluation_context diff --git a/tests/test_streaming_manager.py b/tests/test_streaming_manager.py index 001c964..9b2c249 100644 --- a/tests/test_streaming_manager.py +++ b/tests/test_streaming_manager.py @@ -1,5 +1,5 @@ import time -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import MagicMock, Mock import requests @@ -7,7 +7,8 @@ from pytest_mock import MockerFixture from flagsmith import Flagsmith -from flagsmith.streaming_manager import EventStreamManager, StreamEvent +from flagsmith.streaming_manager import EventStreamManager +from flagsmith.types import StreamEvent def test_stream_manager_handles_timeout( @@ -37,17 +38,15 @@ def test_stream_manager_handles_timeout( def test_environment_updates_on_recent_event( server_api_key: str, mocker: MockerFixture ) -> None: - stream_updated_at = datetime(2020, 1, 1, 1, 1, 2) - environment_updated_at = datetime(2020, 1, 1, 1, 1, 1) + stream_updated_at = datetime(2020, 1, 1, 1, 1, 2, tzinfo=timezone.utc) + environment_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc) mocker.patch("flagsmith.Flagsmith.update_environment") flagsmith = Flagsmith(environment_key=server_api_key) - flagsmith._environment = MagicMock() - flagsmith._environment.updated_at = environment_updated_at - flagsmith.handle_stream_event( - event=StreamEvent(updated_at=stream_updated_at.timestamp()) - ) + flagsmith._evaluation_context = MagicMock() + flagsmith._environment_updated_at = environment_updated_at + flagsmith.handle_stream_event(event=StreamEvent(updated_at=stream_updated_at)) assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_called_once() @@ -55,18 +54,16 @@ def test_environment_updates_on_recent_event( def test_environment_does_not_update_on_past_event( server_api_key: str, mocker: MockerFixture ) -> None: - stream_updated_at = datetime(2020, 1, 1, 1, 1, 1) - environment_updated_at = datetime(2020, 1, 1, 1, 1, 2) + stream_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc) + environment_updated_at = datetime(2020, 1, 1, 1, 1, 2, tzinfo=timezone.utc) mocker.patch("flagsmith.Flagsmith.update_environment") flagsmith = Flagsmith(environment_key=server_api_key) - flagsmith._environment = MagicMock() - flagsmith._environment.updated_at = environment_updated_at + flagsmith._evaluation_context = MagicMock() + flagsmith._environment_updated_at = environment_updated_at - flagsmith.handle_stream_event( - event=StreamEvent(updated_at=stream_updated_at.timestamp()) - ) + flagsmith.handle_stream_event(event=StreamEvent(updated_at=stream_updated_at)) assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_not_called() @@ -74,17 +71,15 @@ def test_environment_does_not_update_on_past_event( def test_environment_does_not_update_on_same_event( server_api_key: str, mocker: MockerFixture ) -> None: - stream_updated_at = datetime(2020, 1, 1, 1, 1, 1) - environment_updated_at = datetime(2020, 1, 1, 1, 1, 1) + stream_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc) + environment_updated_at = datetime(2020, 1, 1, 1, 1, 1, tzinfo=timezone.utc) mocker.patch("flagsmith.Flagsmith.update_environment") flagsmith = Flagsmith(environment_key=server_api_key) - flagsmith._environment = MagicMock() - flagsmith._environment.updated_at = environment_updated_at + flagsmith._evaluation_context = MagicMock() + flagsmith._environment_updated_at = environment_updated_at - flagsmith.handle_stream_event( - event=StreamEvent(updated_at=stream_updated_at.timestamp()) - ) + flagsmith.handle_stream_event(event=StreamEvent(updated_at=stream_updated_at)) assert isinstance(flagsmith.update_environment, Mock) flagsmith.update_environment.assert_not_called() From a0e96c907f3401eb9fd801af05400e4ce92f8feb Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 2 Sep 2025 10:21:43 +0100 Subject: [PATCH 090/121] feat: Standardised `User-Agent` (#152) --- flagsmith/flagsmith.py | 8 +------- tests/test_flagsmith.py | 6 +----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 84be0bb..cdc7b10 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -1,5 +1,4 @@ import logging -import sys import typing from datetime import datetime from urllib.parse import urljoin @@ -7,7 +6,6 @@ import requests from flag_engine import engine from requests.adapters import HTTPAdapter -from requests.utils import default_user_agent from urllib3 import Retry from flagsmith.analytics import AnalyticsProcessor @@ -34,11 +32,7 @@ DEFAULT_API_URL = "https://edge.api.flagsmith.com/api/v1/" DEFAULT_REALTIME_API_URL = "https://realtime.flagsmith.com/" -DEFAULT_USER_AGENT = ( - f"flagsmith-python-client/{__version__} " - + default_user_agent() - + f" python/{sys.version_info.major}.{sys.version_info.minor}" -) +DEFAULT_USER_AGENT = f"flagsmith-python-sdk/{__version__}" class Flagsmith: diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index f87b670..cae9701 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -1,5 +1,4 @@ import json -import sys import time import typing @@ -817,10 +816,7 @@ def test_flagsmith__init__expected_headers_sent( # Then headers = responses.calls[0].request.headers assert headers == { - "User-Agent": ( - f"flagsmith-python-client/{__version__} python-requests/{requests.__version__} " - f"python/{sys.version_info.major}.{sys.version_info.minor}" - ), + "User-Agent": f"flagsmith-python-sdk/{__version__}", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "keep-alive", From 47ec56b9f6dc8c6f5ae7cb1f19e64fa4005dfb2f Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Tue, 2 Sep 2025 18:34:58 +0100 Subject: [PATCH 091/121] chore(main): release 4.0.0 (#149) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 18 ++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 9037ce1..4d20436 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.10.1" + ".": "4.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a46b767..866a5f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [4.0.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.10.1...v4.0.0) (2025-09-02) + +### ⚠ BREAKING CHANGES + +- Engine V7 compatibility ([#150](https://github.com/Flagsmith/flagsmith-python-client/issues/150)) + +### Features + +- Engine V7 compatibility ([#150](https://github.com/Flagsmith/flagsmith-python-client/issues/150)) + ([5ecb078](https://github.com/Flagsmith/flagsmith-python-client/commit/5ecb0788b6c2903826210e0c453d68769220d250)) +- Standardised `User-Agent` ([#152](https://github.com/Flagsmith/flagsmith-python-client/issues/152)) + ([a0e96c9](https://github.com/Flagsmith/flagsmith-python-client/commit/a0e96c907f3401eb9fd801af05400e4ce92f8feb)) + +### Other + +- Add `CODEOWNERS` ([#148](https://github.com/Flagsmith/flagsmith-python-client/issues/148)) + ([a55a921](https://github.com/Flagsmith/flagsmith-python-client/commit/a55a92136699af06390a6570850d45464dcad7aa)) + ## [3.10.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.10.0...v3.10.1) (2025-08-21) ### CI diff --git a/pyproject.toml b/pyproject.toml index 9099882..efdc810 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "3.10.1" +version = "4.0.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 3fcae7c13fb36428ad8137195916f102e39e9453 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 11 Sep 2025 18:06:20 +0100 Subject: [PATCH 092/121] fix: Environment name not mapped to evaluation context (#153) --- flagsmith/mappers.py | 2 +- tests/data/environment.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 0e8fd04..6ccb173 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -80,7 +80,7 @@ def map_environment_document_to_context( return { "environment": { "key": environment_document["api_key"], - "name": "Test Environment", + "name": environment_document["name"], }, "features": { feature["name"]: feature diff --git a/tests/data/environment.json b/tests/data/environment.json index 1cd2245..4a7ac5b 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -1,5 +1,6 @@ { "api_key": "B62qaMZNwfiqT76p38ggrQ", + "name": "Test Environment", "project": { "name": "Test project", "organisation": { From 860b6303cb310166d4bc740fcd8213f4c92164dd Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 19 Sep 2025 09:25:49 +0100 Subject: [PATCH 093/121] fix: Feature state `django_id` fields are not handled (#156) --- flagsmith/mappers.py | 4 +++- tests/data/environment.json | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 6ccb173..3b44f96 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -199,7 +199,9 @@ def _map_environment_document_feature_states_to_feature_contexts( ) -> typing.Iterable[FeatureContext]: for feature_state in feature_states: feature_context = FeatureContext( - key=str(feature_state["id"]), + key=str( + feature_state.get("django_id") or feature_state["featurestate_uuid"] + ), feature_key=str(feature_state["feature"]["id"]), name=feature_state["feature"]["name"], enabled=feature_state["enabled"], diff --git a/tests/data/environment.json b/tests/data/environment.json index 4a7ac5b..ea63981 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -43,8 +43,8 @@ { "multivariate_feature_state_values": [], "feature_state_value": "some-value", - "id": 1, - "featurestate_uuid": "40eb539d-3713-4720-bbd4-829dbef10d51", + "django_id": 1, + "featurestate_uuid": "799d42c3-e973-4d43-957a-35a2aea169c1", "feature": { "name": "some_feature", "type": "STANDARD", From 1c7e8c95146e15c2fba983f8c3517e6dcdbf7c67 Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:17:11 +0100 Subject: [PATCH 094/121] chore(main): release 4.0.1 (#154) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 11 +++++++++++ pyproject.toml | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4d20436..50af31c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.0" + ".": "4.0.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 866a5f5..ced8d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [4.0.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v4.0.0...v4.0.1) (2025-09-19) + +### Bug Fixes + +- Environment name not mapped to evaluation context + ([#153](https://github.com/Flagsmith/flagsmith-python-client/issues/153)) + ([3fcae7c](https://github.com/Flagsmith/flagsmith-python-client/commit/3fcae7c13fb36428ad8137195916f102e39e9453)) +- Feature state `django_id` fields are not handled + ([#156](https://github.com/Flagsmith/flagsmith-python-client/issues/156)) + ([860b630](https://github.com/Flagsmith/flagsmith-python-client/commit/860b6303cb310166d4bc740fcd8213f4c92164dd)) + ## [4.0.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v3.10.1...v4.0.0) (2025-09-02) ### ⚠ BREAKING CHANGES diff --git a/pyproject.toml b/pyproject.toml index efdc810..decf306 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "4.0.0" +version = "4.0.1" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 68d44a15feae75905d08103ff8dba53c605377fd Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Wed, 15 Oct 2025 11:05:08 +0100 Subject: [PATCH 095/121] fix: `get_identity_segments` tries to return identity override segments (#159) --- flagsmith/flagsmith.py | 10 ++--- flagsmith/mappers.py | 45 ++++++++++++++++---- flagsmith/models.py | 9 ++-- flagsmith/offline_handlers.py | 13 +++--- flagsmith/types.py | 13 ++++++ poetry.lock | 16 +++++--- pyproject.toml | 2 +- tests/conftest.py | 4 +- tests/test_flagsmith.py | 42 ++++++++++++++----- tests/test_models.py | 75 +++++++++++++++++++++------------- tests/test_offline_handlers.py | 4 +- 11 files changed, 159 insertions(+), 74 deletions(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index cdc7b10..a27c2c4 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -14,6 +14,7 @@ map_context_and_identity_data_to_context, map_environment_document_to_context, map_environment_document_to_environment_updated_at, + map_segment_results_to_identity_segments, ) from flagsmith.models import DefaultFlag, Flags, Segment from flagsmith.offline_handlers import OfflineHandler @@ -22,6 +23,7 @@ from flagsmith.types import ( ApplicationMetadata, JsonType, + SDKEvaluationContext, StreamEvent, TraitMapping, ) @@ -106,7 +108,7 @@ def __init__( self.default_flag_handler = default_flag_handler self.enable_realtime_updates = enable_realtime_updates self._analytics_processor: typing.Optional[AnalyticsProcessor] = None - self._evaluation_context: typing.Optional[engine.EvaluationContext] = None + self._evaluation_context: typing.Optional[SDKEvaluationContext] = None self._environment_updated_at: typing.Optional[datetime] = None # argument validation @@ -283,10 +285,8 @@ def get_identity_segments( evaluation_result = engine.get_evaluation_result( context=context, ) - return [ - Segment(id=int(segment_result["key"]), name=segment_result["name"]) - for segment_result in evaluation_result["segments"] - ] + + return map_segment_results_to_identity_segments(evaluation_result["segments"]) def update_environment(self) -> None: try: diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 3b44f96..95aa5ed 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -6,14 +6,20 @@ import sseclient from flag_engine.context.types import ( - EvaluationContext, FeatureContext, SegmentContext, SegmentRule, ) +from flag_engine.result.types import SegmentResult from flag_engine.segments.types import ContextValue -from flagsmith.types import StreamEvent, TraitConfig +from flagsmith.models import Segment +from flagsmith.types import ( + SDKEvaluationContext, + SegmentMetadata, + StreamEvent, + TraitConfig, +) OverrideKey = typing.Tuple[ str, @@ -24,6 +30,24 @@ OverridesKey = typing.Tuple[OverrideKey, ...] +def map_segment_results_to_identity_segments( + segment_results: list[SegmentResult[SegmentMetadata]], +) -> list[Segment]: + identity_segments: list[Segment] = [] + for segment_result in segment_results: + if metadata := segment_result.get("metadata"): + if metadata.get("source") == "api" and ( + (flagsmith_id := metadata.get("flagsmith_id")) is not None + ): + identity_segments.append( + Segment( + id=flagsmith_id, + name=segment_result["name"], + ) + ) + return identity_segments + + def map_sse_event_to_stream_event(event: sseclient.Event) -> StreamEvent: event_data = json.loads(event.data) return { @@ -45,7 +69,7 @@ def map_environment_document_to_environment_updated_at( def map_context_and_identity_data_to_context( - context: EvaluationContext, + context: SDKEvaluationContext, identifier: str, traits: typing.Optional[ typing.Mapping[ @@ -56,7 +80,7 @@ def map_context_and_identity_data_to_context( ], ] ], -) -> EvaluationContext: +) -> SDKEvaluationContext: return { **context, "identity": { @@ -76,7 +100,7 @@ def map_context_and_identity_data_to_context( def map_environment_document_to_context( environment_document: dict[str, typing.Any], -) -> EvaluationContext: +) -> SDKEvaluationContext: return { "environment": { "key": environment_document["api_key"], @@ -90,7 +114,7 @@ def map_environment_document_to_context( }, "segments": { **{ - (segment_key := str(segment["id"])): { + (segment_key := str(segment_id := segment["id"])): { "key": segment_key, "name": segment["name"], "rules": _map_environment_document_rules_to_context_rules( @@ -101,6 +125,10 @@ def map_environment_document_to_context( segment.get("feature_states") or [] ) ), + "metadata": SegmentMetadata( + flagsmith_id=segment_id, + source="api", + ), } for segment in environment_document["project"]["segments"] }, @@ -113,7 +141,7 @@ def map_environment_document_to_context( def _map_identity_overrides_to_segments( identity_overrides: list[dict[str, typing.Any]], -) -> dict[str, SegmentContext]: +) -> dict[str, SegmentContext[SegmentMetadata]]: features_to_identifiers: typing.Dict[ OverridesKey, typing.List[str], @@ -137,7 +165,7 @@ def _map_identity_overrides_to_segments( ) ) features_to_identifiers[overrides_key].append(identity_override["identifier"]) - segment_contexts: typing.Dict[str, SegmentContext] = {} + segment_contexts: typing.Dict[str, SegmentContext[SegmentMetadata]] = {} for overrides_key, identifiers in features_to_identifiers.items(): # Create a segment context for each unique set of overrides # Generate a unique key to avoid collisions @@ -168,6 +196,7 @@ def _map_identity_overrides_to_segments( } for feature_key, feature_name, feature_enabled, feature_value in overrides_key ], + metadata=SegmentMetadata(source="identity_overrides"), ) return segment_contexts diff --git a/flagsmith/models.py b/flagsmith/models.py index 3a9ffed..0728e24 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -3,10 +3,11 @@ import typing from dataclasses import dataclass, field -from flag_engine.result.types import EvaluationResult, FlagResult +from flag_engine.result.types import FlagResult from flagsmith.analytics import AnalyticsProcessor from flagsmith.exceptions import FlagsmithFeatureDoesNotExistError +from flagsmith.types import SDKEvaluationResult @dataclass @@ -57,19 +58,19 @@ class Flags: @classmethod def from_evaluation_result( cls, - evaluation_result: EvaluationResult, + evaluation_result: SDKEvaluationResult, analytics_processor: typing.Optional[AnalyticsProcessor], default_flag_handler: typing.Optional[typing.Callable[[str], DefaultFlag]], ) -> Flags: return cls( flags={ - flag["name"]: Flag( + flag_name: Flag( enabled=flag["enabled"], value=flag["value"], feature_name=flag["name"], feature_id=int(flag["feature_key"]), ) - for flag in evaluation_result["flags"] + for flag_name, flag in evaluation_result["flags"].items() }, default_flag_handler=default_flag_handler, _analytics_processor=analytics_processor, diff --git a/flagsmith/offline_handlers.py b/flagsmith/offline_handlers.py index 3248339..6511234 100644 --- a/flagsmith/offline_handlers.py +++ b/flagsmith/offline_handlers.py @@ -2,13 +2,12 @@ from pathlib import Path from typing import Protocol -from flag_engine.context.types import EvaluationContext - from flagsmith.mappers import map_environment_document_to_context +from flagsmith.types import SDKEvaluationContext class OfflineHandler(Protocol): - def get_evaluation_context(self) -> EvaluationContext: ... + def get_evaluation_context(self) -> SDKEvaluationContext: ... class EvaluationContextLocalFileHandler: @@ -21,11 +20,11 @@ class EvaluationContextLocalFileHandler: """ def __init__(self, file_path: str) -> None: - self.evaluation_context: EvaluationContext = json.loads( + self.evaluation_context: SDKEvaluationContext = json.loads( Path(file_path).read_text(), ) - def get_evaluation_context(self) -> EvaluationContext: + def get_evaluation_context(self) -> SDKEvaluationContext: return self.evaluation_context @@ -39,7 +38,7 @@ class EnvironmentDocumentLocalFileHandler: """ def __init__(self, file_path: str) -> None: - self.evaluation_context: EvaluationContext = ( + self.evaluation_context: SDKEvaluationContext = ( map_environment_document_to_context( json.loads( Path(file_path).read_text(), @@ -47,7 +46,7 @@ def __init__(self, file_path: str) -> None: ) ) - def get_evaluation_context(self) -> EvaluationContext: + def get_evaluation_context(self) -> SDKEvaluationContext: return self.evaluation_context diff --git a/flagsmith/types.py b/flagsmith/types.py index d5c4b0f..ed624f3 100644 --- a/flagsmith/types.py +++ b/flagsmith/types.py @@ -1,7 +1,9 @@ import typing from datetime import datetime +from flag_engine.context.types import EvaluationContext from flag_engine.engine import ContextValue +from flag_engine.result.types import EvaluationResult from typing_extensions import NotRequired, TypeAlias _JsonScalarType: TypeAlias = typing.Union[ @@ -33,3 +35,14 @@ class TraitConfig(typing.TypedDict): class ApplicationMetadata(typing.TypedDict): name: NotRequired[str] version: NotRequired[str] + + +class SegmentMetadata(typing.TypedDict): + flagsmith_id: NotRequired[int] + """The ID of the segment used in Flagsmith API.""" + source: NotRequired[typing.Literal["api", "identity_overrides"]] + """The source of the segment, e.g. 'api', 'identity_overrides'.""" + + +SDKEvaluationContext = EvaluationContext[SegmentMetadata] +SDKEvaluationResult = EvaluationResult[SegmentMetadata] diff --git a/poetry.lock b/poetry.lock index a90c934..ef293e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -259,21 +259,25 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "7.0.0" +version = "8.0.0" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] -files = [ - {file = "flagsmith_flag_engine-7.0.0-py3-none-any.whl", hash = "sha256:59c1f13eb920de7182e1992bf4504b7cbdf991fe2391ce42dd8cdda55554ab3a"}, - {file = "flagsmith_flag_engine-7.0.0.tar.gz", hash = "sha256:7db6e6515786a949ceb9036c1b0edcd5cb441aff3d083b4a9648bfd035eab9b5"}, -] +files = [] +develop = false [package.dependencies] jsonpath-rfc9535 = ">=0.1.5,<1" semver = ">=3.0.4,<4" typing-extensions = ">=4.14.1,<5" +[package.source] +type = "git" +url = "https://github.com/Flagsmith/flagsmith-engine.git" +reference = "feat/generic-metadata" +resolved_reference = "7e9d00aec998cb115d4b8175660c79563042a502" + [[package]] name = "identify" version = "2.6.13" @@ -947,4 +951,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "3738927be08947fe27e40be874c3c1429dc98ce072f84468c205e208670bf724" +content-hash = "07576ed467449cc3df826bcec81a02fbd13ab1eeb17a0b6591c451945d3c3239" diff --git a/pyproject.toml b/pyproject.toml index decf306..c589f71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ packages = [{ include = "flagsmith" }] python = ">=3.9,<4" requests = "^2.32.3" requests-futures = "^1.0.1" -flagsmith-flag-engine = "^7.0.0" +flagsmith-flag-engine = { git = "https://github.com/Flagsmith/flagsmith-engine.git", branch = "feat/generic-metadata" } sseclient-py = "^1.8.0" [tool.poetry.group.dev] diff --git a/tests/conftest.py b/tests/conftest.py index 38b98d9..ef7ab22 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,13 +7,13 @@ import pytest import responses -from flag_engine.engine import EvaluationContext from pyfakefs.fake_filesystem import FakeFilesystem from pytest_mock import MockerFixture from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor from flagsmith.mappers import map_environment_document_to_context +from flagsmith.types import SDKEvaluationContext DATA_DIR = os.path.join(os.path.dirname(__file__), "data") @@ -74,7 +74,7 @@ def local_eval_flagsmith( @pytest.fixture() -def evaluation_context(environment_json: str) -> EvaluationContext: +def evaluation_context(environment_json: str) -> SDKEvaluationContext: return map_environment_document_to_context(json.loads(environment_json)) diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index cae9701..27f3e13 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -5,7 +5,6 @@ import pytest import requests import responses -from flag_engine.engine import EvaluationContext from pytest_mock import MockerFixture from responses import matchers @@ -16,6 +15,7 @@ ) from flagsmith.models import DefaultFlag, Flags from flagsmith.offline_handlers import OfflineHandler +from flagsmith.types import SDKEvaluationContext def test_flagsmith_starts_polling_manager_on_init_if_enabled( @@ -39,7 +39,7 @@ def test_flagsmith_starts_polling_manager_on_init_if_enabled( def test_update_environment_sets_environment( flagsmith: Flagsmith, environment_json: str, - evaluation_context: EvaluationContext, + evaluation_context: SDKEvaluationContext, ) -> None: # Given responses.add(method="GET", url=flagsmith.environment_url, body=environment_json) @@ -76,7 +76,7 @@ def test_get_environment_flags_calls_api_when_no_local_environment( @responses.activate() def test_get_environment_flags_uses_local_environment_when_available( flagsmith: Flagsmith, - evaluation_context: EvaluationContext, + evaluation_context: SDKEvaluationContext, ) -> None: # Given flagsmith._evaluation_context = evaluation_context @@ -150,7 +150,7 @@ def test_get_identity_flags_calls_api_when_no_local_environment_with_traits( @responses.activate() def test_get_identity_flags_uses_local_environment_when_available( flagsmith: Flagsmith, - evaluation_context: EvaluationContext, + evaluation_context: SDKEvaluationContext, mocker: MockerFixture, ) -> None: # Given @@ -159,14 +159,14 @@ def test_get_identity_flags_uses_local_environment_when_available( mock_engine = mocker.patch("flagsmith.flagsmith.engine") expected_evaluation_result = { - "flags": [ - { + "flags": { + "some_feature": { "name": "some_feature", "enabled": True, "value": "some-feature-state-value", "feature_key": "1", } - ], + }, "segments": [], } @@ -509,6 +509,26 @@ def test_get_identity_segments_with_valid_trait( assert segments[0].name == "Test segment" # obtained from data/environment.json +def test_get_identity_segments__identity_overrides__returns_expected( + local_eval_flagsmith: Flagsmith, +) -> None: + # Given + # the identifier matches the identity override in data/environment.json + identifier = "overridden-id" + # traits match the "Test segment" segment in data/environment.json + traits = {"foo": "bar"} + + # When + segments = local_eval_flagsmith.get_identity_segments(identifier, traits) + + # Then + # identity override virtual segment is not returned, + # only the segment matching the traits + assert len(segments) == 1 + assert segments[0].id == 1 + assert segments[0].name == "Test segment" + + def test_local_evaluation_requires_server_key() -> None: with pytest.raises(ValueError): Flagsmith(environment_key="not-a-server-key", enable_local_evaluation=True) @@ -525,10 +545,10 @@ def test_initialise_flagsmith_with_proxies() -> None: assert flagsmith.session.proxies == proxies -def test_offline_mode(evaluation_context: EvaluationContext) -> None: +def test_offline_mode(evaluation_context: SDKEvaluationContext) -> None: # Given class DummyOfflineHandler: - def get_evaluation_context(self) -> EvaluationContext: + def get_evaluation_context(self) -> SDKEvaluationContext: return evaluation_context # When @@ -546,7 +566,7 @@ def get_evaluation_context(self) -> EvaluationContext: @responses.activate() def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( mocker: MockerFixture, - evaluation_context: EvaluationContext, + evaluation_context: SDKEvaluationContext, ) -> None: # Given api_url = "http://some.flagsmith.com/api/v1/" @@ -579,7 +599,7 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( @responses.activate() def test_offline_mode__local_evaluation__correct_fallback( mocker: MockerFixture, - evaluation_context: EvaluationContext, + evaluation_context: SDKEvaluationContext, caplog: pytest.LogCaptureFixture, ) -> None: # Given diff --git a/tests/test_models.py b/tests/test_models.py index b249268..7a3c48e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,18 +1,20 @@ import typing import pytest -from flag_engine.result.types import EvaluationResult, FlagResult +from flag_engine.result.types import FlagResult from flagsmith.models import Flag, Flags +from flagsmith.types import SDKEvaluationResult def test_flag_from_evaluation_result() -> None: # Given flag_result: FlagResult = { - "name": "test_feature", "enabled": True, - "value": "test-value", "feature_key": "123", + "name": "test_feature", + "reason": "DEFAULT", + "value": "test-value", } # When @@ -29,55 +31,71 @@ def test_flag_from_evaluation_result() -> None: @pytest.mark.parametrize( "flags_result,expected_count,expected_names", [ - ([], 0, []), + ({}, 0, []), ( - [ - { - "name": "feature1", + { + "feature1": { "enabled": True, - "value": "value1", "feature_key": "1", + "name": "feature1", + "reason": "DEFAULT", + "value": "value1", } - ], + }, 1, ["feature1"], ), ( - [ - { - "name": "feature1", + { + "feature1": { "enabled": True, + "feature_key": "1", + "name": "feature1", + "reason": "DEFAULT", "value": "value1", + } + }, + 1, + ["feature1"], + ), + ( + { + "feature1": { + "enabled": True, "feature_key": "1", + "name": "feature1", + "reason": "DEFAULT", + "value": "value1", }, - { - "name": "feature2", - "enabled": False, - "value": None, + "feature2": { + "enabled": True, "feature_key": "2", + "name": "feature2", + "reason": "DEFAULT", + "value": "value2", + }, + "feature3": { + "enabled": True, + "feature_key": "3", + "name": "feature3", + "reason": "DEFAULT", + "value": 42, }, - {"name": "feature3", "enabled": True, "value": 42, "feature_key": "3"}, - ], + }, 3, ["feature1", "feature2", "feature3"], ), ], ) def test_flags_from_evaluation_result( - flags_result: typing.List[FlagResult], + flags_result: typing.Dict[str, FlagResult], expected_count: int, expected_names: typing.List[str], ) -> None: # Given - evaluation_result: EvaluationResult = { + evaluation_result: SDKEvaluationResult = { "flags": flags_result, "segments": [], - "context": { - "environment": { - "name": "test_environment", - "key": "test_environment_key", - } - }, } # When @@ -113,10 +131,11 @@ def test_flag_from_evaluation_result_value_types( ) -> None: # Given flag_result: FlagResult = { - "name": "test_feature", "enabled": True, - "value": value, "feature_key": "123", + "name": "test_feature", + "reason": "DEFAULT", + "value": value, } # When diff --git a/tests/test_offline_handlers.py b/tests/test_offline_handlers.py index 4d77ff3..9da5dc9 100644 --- a/tests/test_offline_handlers.py +++ b/tests/test_offline_handlers.py @@ -1,12 +1,12 @@ -from flag_engine.engine import EvaluationContext from pyfakefs.fake_filesystem import FakeFilesystem from flagsmith.offline_handlers import LocalFileHandler +from flagsmith.types import SDKEvaluationContext def test_local_file_handler( fs: FakeFilesystem, - evaluation_context: EvaluationContext, + evaluation_context: SDKEvaluationContext, environment_json: str, ) -> None: # Given From 374e29293aca44eafadda672907d9b701b8414fc Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 16 Oct 2025 10:04:33 +0100 Subject: [PATCH 096/121] feat!: Restore v3 `OfflineHandler` interface (#162) --- flagsmith/api/types.py | 69 ++++++++++++++++++++++++++++++++++ flagsmith/flagsmith.py | 4 +- flagsmith/mappers.py | 31 ++++++++------- flagsmith/offline_handlers.py | 47 +++++++---------------- poetry.lock | 8 ++-- pyproject.toml | 3 +- tests/conftest.py | 11 +++++- tests/test_flagsmith.py | 19 +++++----- tests/test_offline_handlers.py | 21 +++++++++-- 9 files changed, 145 insertions(+), 68 deletions(-) create mode 100644 flagsmith/api/types.py diff --git a/flagsmith/api/types.py b/flagsmith/api/types.py new file mode 100644 index 0000000..fda43fe --- /dev/null +++ b/flagsmith/api/types.py @@ -0,0 +1,69 @@ +import typing + +from flag_engine.segments.types import ConditionOperator, RuleType +from typing_extensions import NotRequired + + +class SegmentConditionModel(typing.TypedDict): + operator: ConditionOperator + property_: str + value: str + + +class SegmentRuleModel(typing.TypedDict): + conditions: "list[SegmentConditionModel]" + rules: "list[SegmentRuleModel]" + type: RuleType + + +class SegmentModel(typing.TypedDict): + id: int + name: str + rules: list[SegmentRuleModel] + feature_states: "NotRequired[list[FeatureStateModel]]" + + +class ProjectModel(typing.TypedDict): + segments: list[SegmentModel] + + +class FeatureModel(typing.TypedDict): + id: int + name: str + + +class FeatureSegmentModel(typing.TypedDict): + priority: int + + +class MultivariateFeatureOptionModel(typing.TypedDict): + value: str + + +class MultivariateFeatureStateValueModel(typing.TypedDict): + id: typing.Optional[int] + multivariate_feature_option: MultivariateFeatureOptionModel + mv_fs_value_uuid: str + percentage_allocation: float + + +class FeatureStateModel(typing.TypedDict): + enabled: bool + feature_segment: NotRequired[FeatureSegmentModel] + feature_state_value: object + feature: FeatureModel + featurestate_uuid: str + multivariate_feature_state_values: list[MultivariateFeatureStateValueModel] + + +class IdentityModel(typing.TypedDict): + identifier: str + identity_features: list[FeatureStateModel] + + +class EnvironmentModel(typing.TypedDict): + api_key: str + feature_states: list[FeatureStateModel] + identity_overrides: list[IdentityModel] + name: str + project: ProjectModel diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index a27c2c4..8eaf847 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -125,7 +125,9 @@ def __init__( ) if self.offline_handler: - self._evaluation_context = self.offline_handler.get_evaluation_context() + self._evaluation_context = map_environment_document_to_context( + self.offline_handler.get_environment() + ) if not self.offline_mode: if not environment_key: diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 95aa5ed..1bdd8ae 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -9,10 +9,17 @@ FeatureContext, SegmentContext, SegmentRule, + StrValueSegmentCondition, ) from flag_engine.result.types import SegmentResult from flag_engine.segments.types import ContextValue +from flagsmith.api.types import ( + EnvironmentModel, + FeatureStateModel, + IdentityModel, + SegmentRuleModel, +) from flagsmith.models import Segment from flagsmith.types import ( SDKEvaluationContext, @@ -99,7 +106,7 @@ def map_context_and_identity_data_to_context( def map_environment_document_to_context( - environment_document: dict[str, typing.Any], + environment_document: EnvironmentModel, ) -> SDKEvaluationContext: return { "environment": { @@ -140,16 +147,14 @@ def map_environment_document_to_context( def _map_identity_overrides_to_segments( - identity_overrides: list[dict[str, typing.Any]], + identity_overrides: list[IdentityModel], ) -> dict[str, SegmentContext[SegmentMetadata]]: features_to_identifiers: typing.Dict[ OverridesKey, typing.List[str], ] = defaultdict(list) for identity_override in identity_overrides: - identity_features: list[dict[str, typing.Any]] = identity_override[ - "identity_features" - ] + identity_features = identity_override["identity_features"] if not identity_features: continue overrides_key = tuple( @@ -202,14 +207,14 @@ def _map_identity_overrides_to_segments( def _map_environment_document_rules_to_context_rules( - rules: list[dict[str, typing.Any]], + rules: list[SegmentRuleModel], ) -> list[SegmentRule]: return [ dict( type=rule["type"], conditions=[ - dict( - property=condition.get("property_"), + StrValueSegmentCondition( + property=condition.get("property_") or "", operator=condition["operator"], value=condition["value"], ) @@ -224,7 +229,7 @@ def _map_environment_document_rules_to_context_rules( def _map_environment_document_feature_states_to_feature_contexts( - feature_states: list[dict[str, typing.Any]], + feature_states: list[FeatureStateModel], ) -> typing.Iterable[FeatureContext]: for feature_state in feature_states: feature_context = FeatureContext( @@ -251,10 +256,8 @@ def _map_environment_document_feature_states_to_feature_contexts( key=itemgetter("id"), ) ] - if ( - priority := (feature_state.get("feature_segment") or {}).get("priority") - is not None - ): - feature_context["priority"] = priority + + if "feature_segment" in feature_state: + feature_context["priority"] = feature_state["feature_segment"]["priority"] yield feature_context diff --git a/flagsmith/offline_handlers.py b/flagsmith/offline_handlers.py index 6511234..3920120 100644 --- a/flagsmith/offline_handlers.py +++ b/flagsmith/offline_handlers.py @@ -1,34 +1,23 @@ import json +from abc import ABC, abstractmethod from pathlib import Path from typing import Protocol +from flagsmith.api.types import EnvironmentModel from flagsmith.mappers import map_environment_document_to_context -from flagsmith.types import SDKEvaluationContext class OfflineHandler(Protocol): - def get_evaluation_context(self) -> SDKEvaluationContext: ... + def get_environment(self) -> EnvironmentModel: ... -class EvaluationContextLocalFileHandler: - """ - Handler to load evaluation context from a local JSON file. - The JSON file should contain the full evaluation context as per Flagsmith Engine's specification. - - JSON schema: - https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-context.json - """ - - def __init__(self, file_path: str) -> None: - self.evaluation_context: SDKEvaluationContext = json.loads( - Path(file_path).read_text(), - ) - - def get_evaluation_context(self) -> SDKEvaluationContext: - return self.evaluation_context +class BaseOfflineHandler(ABC): + @abstractmethod + def get_environment(self) -> EnvironmentModel: + raise NotImplementedError() -class EnvironmentDocumentLocalFileHandler: +class LocalFileHandler: """ Handler to load evaluation context from a local JSON file containing the environment document. The JSON file should contain the environment document as returned by the Flagsmith API. @@ -38,18 +27,10 @@ class EnvironmentDocumentLocalFileHandler: """ def __init__(self, file_path: str) -> None: - self.evaluation_context: SDKEvaluationContext = ( - map_environment_document_to_context( - json.loads( - Path(file_path).read_text(), - ), - ) - ) - - def get_evaluation_context(self) -> SDKEvaluationContext: - return self.evaluation_context - + environment_document = json.loads(Path(file_path).read_text()) + # Make sure the document can be used for evaluation + map_environment_document_to_context(environment_document) + self.environment_document: EnvironmentModel = environment_document -# For backward compatibility, use the old class name for -# the local file handler implementation dependant on the environment document. -LocalFileHandler = EnvironmentDocumentLocalFileHandler + def get_environment(self) -> EnvironmentModel: + return self.environment_document diff --git a/poetry.lock b/poetry.lock index ef293e0..5ee3591 100644 --- a/poetry.lock +++ b/poetry.lock @@ -898,14 +898,14 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, - {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] @@ -951,4 +951,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "07576ed467449cc3df826bcec81a02fbd13ab1eeb17a0b6591c451945d3c3239" +content-hash = "702545ad27e44d6d5bdc0a4cef9517a70a2548858cc8ea5ca2410d78ef296b9e" diff --git a/pyproject.toml b/pyproject.toml index c589f71..f3c228a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,11 +10,12 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] +flagsmith-flag-engine = { git = "https://github.com/Flagsmith/flagsmith-engine.git", branch = "feat/generic-metadata" } python = ">=3.9,<4" requests = "^2.32.3" requests-futures = "^1.0.1" -flagsmith-flag-engine = { git = "https://github.com/Flagsmith/flagsmith-engine.git", branch = "feat/generic-metadata" } sseclient-py = "^1.8.0" +typing-extensions = "^4.15.0" [tool.poetry.group.dev] optional = true diff --git a/tests/conftest.py b/tests/conftest.py index ef7ab22..ce1153c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ from flagsmith import Flagsmith from flagsmith.analytics import AnalyticsProcessor +from flagsmith.api.types import EnvironmentModel from flagsmith.mappers import map_environment_document_to_context from flagsmith.types import SDKEvaluationContext @@ -74,8 +75,14 @@ def local_eval_flagsmith( @pytest.fixture() -def evaluation_context(environment_json: str) -> SDKEvaluationContext: - return map_environment_document_to_context(json.loads(environment_json)) +def environment(environment_json: str) -> EnvironmentModel: + ret: EnvironmentModel = json.loads(environment_json) + return ret + + +@pytest.fixture() +def evaluation_context(environment: EnvironmentModel) -> SDKEvaluationContext: + return map_environment_document_to_context(environment) @pytest.fixture() diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 27f3e13..00d1ae8 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -9,6 +9,7 @@ from responses import matchers from flagsmith import Flagsmith, __version__ +from flagsmith.api.types import EnvironmentModel from flagsmith.exceptions import ( FlagsmithAPIError, FlagsmithFeatureDoesNotExistError, @@ -545,11 +546,11 @@ def test_initialise_flagsmith_with_proxies() -> None: assert flagsmith.session.proxies == proxies -def test_offline_mode(evaluation_context: SDKEvaluationContext) -> None: +def test_offline_mode(environment: EnvironmentModel) -> None: # Given class DummyOfflineHandler: - def get_evaluation_context(self) -> SDKEvaluationContext: - return evaluation_context + def get_environment(self) -> EnvironmentModel: + return environment # When flagsmith = Flagsmith(offline_mode=True, offline_handler=DummyOfflineHandler()) @@ -566,12 +567,12 @@ def get_evaluation_context(self) -> SDKEvaluationContext: @responses.activate() def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( mocker: MockerFixture, - evaluation_context: SDKEvaluationContext, + environment: EnvironmentModel, ) -> None: # Given api_url = "http://some.flagsmith.com/api/v1/" mock_offline_handler = mocker.MagicMock(spec=OfflineHandler) - mock_offline_handler.get_evaluation_context.return_value = evaluation_context + mock_offline_handler.get_environment.return_value = environment flagsmith = Flagsmith( environment_key="some-key", @@ -587,7 +588,7 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( identity_flags = flagsmith.get_identity_flags("identity", traits={}) # Then - mock_offline_handler.get_evaluation_context.assert_called_once_with() + mock_offline_handler.get_environment.assert_called_once_with() assert environment_flags.is_feature_enabled("some_feature") is True assert environment_flags.get_feature_value("some_feature") == "some-value" @@ -599,13 +600,13 @@ def test_flagsmith_uses_offline_handler_if_set_and_no_api_response( @responses.activate() def test_offline_mode__local_evaluation__correct_fallback( mocker: MockerFixture, - evaluation_context: SDKEvaluationContext, + environment: EnvironmentModel, caplog: pytest.LogCaptureFixture, ) -> None: # Given api_url = "http://some.flagsmith.com/api/v1/" mock_offline_handler = mocker.MagicMock(spec=OfflineHandler) - mock_offline_handler.get_evaluation_context.return_value = evaluation_context + mock_offline_handler.get_environment.return_value = environment mocker.patch("flagsmith.flagsmith.EnvironmentDataPollingManager") @@ -623,7 +624,7 @@ def test_offline_mode__local_evaluation__correct_fallback( identity_flags = flagsmith.get_identity_flags("identity", traits={}) # Then - mock_offline_handler.get_evaluation_context.assert_called_once_with() + mock_offline_handler.get_environment.assert_called_once_with() assert environment_flags.is_feature_enabled("some_feature") is True assert environment_flags.get_feature_value("some_feature") == "some-value" diff --git a/tests/test_offline_handlers.py b/tests/test_offline_handlers.py index 9da5dc9..81e9cda 100644 --- a/tests/test_offline_handlers.py +++ b/tests/test_offline_handlers.py @@ -1,12 +1,13 @@ +import json + +import pytest from pyfakefs.fake_filesystem import FakeFilesystem from flagsmith.offline_handlers import LocalFileHandler -from flagsmith.types import SDKEvaluationContext def test_local_file_handler( fs: FakeFilesystem, - evaluation_context: SDKEvaluationContext, environment_json: str, ) -> None: # Given @@ -15,7 +16,19 @@ def test_local_file_handler( local_file_handler = LocalFileHandler(environment_document_file_path) # When - result = local_file_handler.get_evaluation_context() + result = local_file_handler.get_environment() # Then - assert result == evaluation_context + assert result == json.loads(environment_json) + + +def test_local_file_handler__invalid_contents__raises_expected( + fs: FakeFilesystem, +) -> None: + # Given + environment_document_file_path = "/some/path/environment.json" + fs.create_file(environment_document_file_path, contents="{}") + + # When & Then + with pytest.raises(KeyError): + LocalFileHandler(environment_document_file_path) From 4f84044ea87a9d284f6731ff4cfe4835d5f99fa4 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 16 Oct 2025 18:48:49 +0100 Subject: [PATCH 097/121] feat: Support variant priority (#161) --- flagsmith/mappers.py | 13 ++++++++----- poetry.lock | 16 ++++++---------- pyproject.toml | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 1bdd8ae..ec771c3 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -1,8 +1,8 @@ import json import typing +import uuid from collections import defaultdict from datetime import datetime, timezone -from operator import itemgetter import sseclient from flag_engine.context.types import ( @@ -250,11 +250,14 @@ def _map_environment_document_feature_states_to_feature_contexts( "multivariate_feature_option" ]["value"], "weight": multivariate_feature_state_value["percentage_allocation"], + "priority": ( + multivariate_feature_state_value.get("id") + or uuid.UUID( + multivariate_feature_state_value["mv_fs_value_uuid"] + ).int + ), } - for multivariate_feature_state_value in sorted( - multivariate_feature_state_values, - key=itemgetter("id"), - ) + for multivariate_feature_state_value in multivariate_feature_state_values ] if "feature_segment" in feature_state: diff --git a/poetry.lock b/poetry.lock index 5ee3591..7173d15 100644 --- a/poetry.lock +++ b/poetry.lock @@ -259,25 +259,21 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "8.0.0" +version = "9.0.0" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] -files = [] -develop = false +files = [ + {file = "flagsmith_flag_engine-9.0.0-py3-none-any.whl", hash = "sha256:2775106adb09f2f6fdeaccdcc1e84a254df139352eb442f8cb66c536c44ca986"}, + {file = "flagsmith_flag_engine-9.0.0.tar.gz", hash = "sha256:0cf8450e9a006cffbc65e4442fbe73e39860f6101f04fc9629a569a354d857ad"}, +] [package.dependencies] jsonpath-rfc9535 = ">=0.1.5,<1" semver = ">=3.0.4,<4" typing-extensions = ">=4.14.1,<5" -[package.source] -type = "git" -url = "https://github.com/Flagsmith/flagsmith-engine.git" -reference = "feat/generic-metadata" -resolved_reference = "7e9d00aec998cb115d4b8175660c79563042a502" - [[package]] name = "identify" version = "2.6.13" @@ -951,4 +947,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "702545ad27e44d6d5bdc0a4cef9517a70a2548858cc8ea5ca2410d78ef296b9e" +content-hash = "66865353f740f4b6fa02982ead5929aaf5105201b8f9f590651e37fc424ff79d" diff --git a/pyproject.toml b/pyproject.toml index f3c228a..16528d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -flagsmith-flag-engine = { git = "https://github.com/Flagsmith/flagsmith-engine.git", branch = "feat/generic-metadata" } +flagsmith-flag-engine = "^9.0.0" python = ">=3.9,<4" requests = "^2.32.3" requests-futures = "^1.0.1" From 1bbbdf8d98054ea4317a1ba3bd95f437a7edbf0e Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Wed, 22 Oct 2025 18:19:32 +0100 Subject: [PATCH 098/121] feat: Support feature metadata (#163) --- flagsmith/mappers.py | 26 ++++++++++++++------ flagsmith/models.py | 31 ++++++++++++----------- flagsmith/types.py | 12 ++++++--- poetry.lock | 8 +++--- pyproject.toml | 2 +- tests/test_flagsmith.py | 1 + tests/test_models.py | 54 ++++++++++++++++++++++++++--------------- 7 files changed, 82 insertions(+), 52 deletions(-) diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index ec771c3..8d8179a 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -22,6 +22,7 @@ ) from flagsmith.models import Segment from flagsmith.types import ( + FeatureMetadata, SDKEvaluationContext, SegmentMetadata, StreamEvent, @@ -29,7 +30,7 @@ ) OverrideKey = typing.Tuple[ - str, + int, str, bool, typing.Any, @@ -148,7 +149,7 @@ def map_environment_document_to_context( def _map_identity_overrides_to_segments( identity_overrides: list[IdentityModel], -) -> dict[str, SegmentContext[SegmentMetadata]]: +) -> dict[str, SegmentContext[SegmentMetadata, FeatureMetadata]]: features_to_identifiers: typing.Dict[ OverridesKey, typing.List[str], @@ -159,7 +160,7 @@ def _map_identity_overrides_to_segments( continue overrides_key = tuple( ( - str(feature_state["feature"]["id"]), + feature_state["feature"]["id"], feature_state["feature"]["name"], feature_state["enabled"], feature_state["feature_state_value"], @@ -170,7 +171,13 @@ def _map_identity_overrides_to_segments( ) ) features_to_identifiers[overrides_key].append(identity_override["identifier"]) - segment_contexts: typing.Dict[str, SegmentContext[SegmentMetadata]] = {} + segment_contexts: typing.Dict[ + str, + SegmentContext[ + SegmentMetadata, + FeatureMetadata, + ], + ] = {} for overrides_key, identifiers in features_to_identifiers.items(): # Create a segment context for each unique set of overrides # Generate a unique key to avoid collisions @@ -193,13 +200,14 @@ def _map_identity_overrides_to_segments( overrides=[ { "key": "", # Identity overrides never carry multivariate options - "feature_key": feature_key, + "feature_key": str(flagsmith_id), "name": feature_name, "enabled": feature_enabled, "value": feature_value, "priority": float("-inf"), # Highest possible priority + "metadata": {"flagsmith_id": flagsmith_id}, } - for feature_key, feature_name, feature_enabled, feature_value in overrides_key + for flagsmith_id, feature_name, feature_enabled, feature_value in overrides_key ], metadata=SegmentMetadata(source="identity_overrides"), ) @@ -230,9 +238,10 @@ def _map_environment_document_rules_to_context_rules( def _map_environment_document_feature_states_to_feature_contexts( feature_states: list[FeatureStateModel], -) -> typing.Iterable[FeatureContext]: +) -> typing.Iterable[FeatureContext[FeatureMetadata]]: for feature_state in feature_states: - feature_context = FeatureContext( + metadata: FeatureMetadata = {"flagsmith_id": feature_state["feature"]["id"]} + feature_context = FeatureContext[FeatureMetadata]( key=str( feature_state.get("django_id") or feature_state["featurestate_uuid"] ), @@ -240,6 +249,7 @@ def _map_environment_document_feature_states_to_feature_contexts( name=feature_state["feature"]["name"], enabled=feature_state["enabled"], value=feature_state["feature_state_value"], + metadata=metadata, ) if multivariate_feature_state_values := feature_state.get( "multivariate_feature_state_values" diff --git a/flagsmith/models.py b/flagsmith/models.py index 0728e24..ab69d2d 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -3,11 +3,9 @@ import typing from dataclasses import dataclass, field -from flag_engine.result.types import FlagResult - from flagsmith.analytics import AnalyticsProcessor from flagsmith.exceptions import FlagsmithFeatureDoesNotExistError -from flagsmith.types import SDKEvaluationResult +from flagsmith.types import SDKEvaluationResult, SDKFlagResult @dataclass @@ -30,13 +28,18 @@ class Flag(BaseFlag): @classmethod def from_evaluation_result( cls, - flag: FlagResult, + flag_result: SDKFlagResult, ) -> Flag: - return Flag( - enabled=flag["enabled"], - value=flag["value"], - feature_name=flag["name"], - feature_id=int(flag["feature_key"]), + if metadata := flag_result.get("metadata"): + return Flag( + enabled=flag_result["enabled"], + value=flag_result["value"], + feature_name=flag_result["name"], + feature_id=metadata["flagsmith_id"], + ) + raise ValueError( + "FlagResult metadata is missing. Cannot create Flag instance. " + "This means a bug in the SDK, please report it." ) @classmethod @@ -64,13 +67,9 @@ def from_evaluation_result( ) -> Flags: return cls( flags={ - flag_name: Flag( - enabled=flag["enabled"], - value=flag["value"], - feature_name=flag["name"], - feature_id=int(flag["feature_key"]), - ) - for flag_name, flag in evaluation_result["flags"].items() + flag_name: flag + for flag_name, flag_result in evaluation_result["flags"].items() + if (flag := Flag.from_evaluation_result(flag_result)) }, default_flag_handler=default_flag_handler, _analytics_processor=analytics_processor, diff --git a/flagsmith/types.py b/flagsmith/types.py index ed624f3..668d9df 100644 --- a/flagsmith/types.py +++ b/flagsmith/types.py @@ -3,7 +3,7 @@ from flag_engine.context.types import EvaluationContext from flag_engine.engine import ContextValue -from flag_engine.result.types import EvaluationResult +from flag_engine.result.types import EvaluationResult, FlagResult from typing_extensions import NotRequired, TypeAlias _JsonScalarType: TypeAlias = typing.Union[ @@ -44,5 +44,11 @@ class SegmentMetadata(typing.TypedDict): """The source of the segment, e.g. 'api', 'identity_overrides'.""" -SDKEvaluationContext = EvaluationContext[SegmentMetadata] -SDKEvaluationResult = EvaluationResult[SegmentMetadata] +class FeatureMetadata(typing.TypedDict): + flagsmith_id: int + """The ID of the feature used in Flagsmith API.""" + + +SDKEvaluationContext = EvaluationContext[SegmentMetadata, FeatureMetadata] +SDKEvaluationResult = EvaluationResult[SegmentMetadata, FeatureMetadata] +SDKFlagResult = FlagResult[FeatureMetadata] diff --git a/poetry.lock b/poetry.lock index 7173d15..0638cba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -259,14 +259,14 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "9.0.0" +version = "9.1.0" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith_flag_engine-9.0.0-py3-none-any.whl", hash = "sha256:2775106adb09f2f6fdeaccdcc1e84a254df139352eb442f8cb66c536c44ca986"}, - {file = "flagsmith_flag_engine-9.0.0.tar.gz", hash = "sha256:0cf8450e9a006cffbc65e4442fbe73e39860f6101f04fc9629a569a354d857ad"}, + {file = "flagsmith_flag_engine-9.1.0-py3-none-any.whl", hash = "sha256:1afe9aa37469ce4208f5e62c2a90669e2fdada401d4b795b339ea9bf019da733"}, + {file = "flagsmith_flag_engine-9.1.0.tar.gz", hash = "sha256:d18f8daef0684f1b0224a9f98c279c966cafd29d52444682705c474247d3b4ce"}, ] [package.dependencies] @@ -947,4 +947,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "66865353f740f4b6fa02982ead5929aaf5105201b8f9f590651e37fc424ff79d" +content-hash = "7feced3e0ab64b956db1c6eaf112aa2553494bf4a27127a5afd0a8062d952917" diff --git a/pyproject.toml b/pyproject.toml index 16528d0..c1df2ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -flagsmith-flag-engine = "^9.0.0" +flagsmith-flag-engine = "^9.1.0" python = ">=3.9,<4" requests = "^2.32.3" requests-futures = "^1.0.1" diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 00d1ae8..6d5258c 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -166,6 +166,7 @@ def test_get_identity_flags_uses_local_environment_when_available( "enabled": True, "value": "some-feature-state-value", "feature_key": "1", + "metadata": {"flagsmith_id": 1}, } }, "segments": [], diff --git a/tests/test_models.py b/tests/test_models.py index 7a3c48e..35d960d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,24 +1,24 @@ import typing import pytest -from flag_engine.result.types import FlagResult from flagsmith.models import Flag, Flags -from flagsmith.types import SDKEvaluationResult +from flagsmith.types import SDKEvaluationResult, SDKFlagResult def test_flag_from_evaluation_result() -> None: # Given - flag_result: FlagResult = { + flag_result: SDKFlagResult = { "enabled": True, "feature_key": "123", "name": "test_feature", "reason": "DEFAULT", "value": "test-value", + "metadata": {"flagsmith_id": 123}, } # When - flag: Flag = Flag.from_evaluation_result(flag_result) + flag = Flag.from_evaluation_result(flag_result) # Then assert flag.enabled is True @@ -29,9 +29,9 @@ def test_flag_from_evaluation_result() -> None: @pytest.mark.parametrize( - "flags_result,expected_count,expected_names", + "flags_result,expected_names", [ - ({}, 0, []), + ({}, []), ( { "feature1": { @@ -40,9 +40,9 @@ def test_flag_from_evaluation_result() -> None: "name": "feature1", "reason": "DEFAULT", "value": "value1", + "metadata": {"flagsmith_id": 1}, } }, - 1, ["feature1"], ), ( @@ -53,9 +53,9 @@ def test_flag_from_evaluation_result() -> None: "name": "feature1", "reason": "DEFAULT", "value": "value1", + "metadata": {"flagsmith_id": 1}, } }, - 1, ["feature1"], ), ( @@ -66,6 +66,7 @@ def test_flag_from_evaluation_result() -> None: "name": "feature1", "reason": "DEFAULT", "value": "value1", + "metadata": {"flagsmith_id": 1}, }, "feature2": { "enabled": True, @@ -73,6 +74,7 @@ def test_flag_from_evaluation_result() -> None: "name": "feature2", "reason": "DEFAULT", "value": "value2", + "metadata": {"flagsmith_id": 2}, }, "feature3": { "enabled": True, @@ -80,16 +82,15 @@ def test_flag_from_evaluation_result() -> None: "name": "feature3", "reason": "DEFAULT", "value": 42, + "metadata": {"flagsmith_id": 3}, }, }, - 3, ["feature1", "feature2", "feature3"], ), ], ) def test_flags_from_evaluation_result( - flags_result: typing.Dict[str, FlagResult], - expected_count: int, + flags_result: typing.Dict[str, SDKFlagResult], expected_names: typing.List[str], ) -> None: # Given @@ -106,13 +107,10 @@ def test_flags_from_evaluation_result( ) # Then - assert len(flags.flags) == expected_count - - for name in expected_names: - assert name in flags.flags - flag: Flag = flags.flags[name] - assert isinstance(flag, Flag) - assert flag.feature_name == name + assert set(flags.flags.keys()) == set(expected_names) + assert set(flag.feature_name for flag in flags.flags.values()) == set( + expected_names + ) @pytest.mark.parametrize( @@ -130,16 +128,32 @@ def test_flag_from_evaluation_result_value_types( value: typing.Any, expected: typing.Any ) -> None: # Given - flag_result: FlagResult = { + flag_result: SDKFlagResult = { "enabled": True, "feature_key": "123", "name": "test_feature", "reason": "DEFAULT", "value": value, + "metadata": {"flagsmith_id": 123}, } # When - flag: Flag = Flag.from_evaluation_result(flag_result) + flag = Flag.from_evaluation_result(flag_result) # Then assert flag.value == expected + + +def test_flag_from_evaluation_result_missing_metadata__raises_expected() -> None: + # Given + flag_result: SDKFlagResult = { + "enabled": True, + "feature_key": "123", + "name": "test_feature", + "reason": "DEFAULT", + "value": "test-value", + } + + # When & Then + with pytest.raises(ValueError): + Flag.from_evaluation_result(flag_result) From e2fe6eb01ba61a2477d54e75abc636c00c7f1e10 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:20:33 +0100 Subject: [PATCH 099/121] ci: pre-commit autoupdate (#158) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 207279f..d310844 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - repo: https://github.com/PyCQA/isort - rev: 6.0.1 + rev: 7.0.0 hooks: - id: isort - - repo: https://github.com/psf/black - rev: 25.1.0 + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 25.9.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 From 5de965027d94cd4cd62178615c7e6d14c5340b70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:21:13 +0100 Subject: [PATCH 100/121] deps: bump urllib3 from 2.2.3 to 2.5.0 (#157) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0638cba..7b7aedf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. [[package]] name = "certifi" @@ -906,14 +906,14 @@ files = [ [[package]] name = "urllib3" -version = "2.2.3" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] From 68c40b554d4cbc9d7e3b5e31f7238de253648db1 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Fri, 24 Oct 2025 17:36:16 +0100 Subject: [PATCH 101/121] deps: Bump `flagsmith-flag-engine` to 10.0.0 (#165) --- flagsmith/mappers.py | 3 --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- tests/test_flagsmith.py | 1 - tests/test_models.py | 8 -------- 5 files changed, 6 insertions(+), 18 deletions(-) diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 8d8179a..7564a89 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -93,7 +93,6 @@ def map_context_and_identity_data_to_context( **context, "identity": { "identifier": identifier, - "key": f"{context['environment']['key']}_{identifier}", "traits": { trait_key: ( trait_value_or_config["value"] @@ -200,7 +199,6 @@ def _map_identity_overrides_to_segments( overrides=[ { "key": "", # Identity overrides never carry multivariate options - "feature_key": str(flagsmith_id), "name": feature_name, "enabled": feature_enabled, "value": feature_value, @@ -245,7 +243,6 @@ def _map_environment_document_feature_states_to_feature_contexts( key=str( feature_state.get("django_id") or feature_state["featurestate_uuid"] ), - feature_key=str(feature_state["feature"]["id"]), name=feature_state["feature"]["name"], enabled=feature_state["enabled"], value=feature_state["feature_state_value"], diff --git a/poetry.lock b/poetry.lock index 7b7aedf..4d9f539 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "certifi" @@ -259,14 +259,14 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "9.1.0" +version = "10.0.0" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith_flag_engine-9.1.0-py3-none-any.whl", hash = "sha256:1afe9aa37469ce4208f5e62c2a90669e2fdada401d4b795b339ea9bf019da733"}, - {file = "flagsmith_flag_engine-9.1.0.tar.gz", hash = "sha256:d18f8daef0684f1b0224a9f98c279c966cafd29d52444682705c474247d3b4ce"}, + {file = "flagsmith_flag_engine-10.0.0-py3-none-any.whl", hash = "sha256:da9dec37556416a6bec2004ec6dbe7e2581dfdd24167fe224a2280db7f01c011"}, + {file = "flagsmith_flag_engine-10.0.0.tar.gz", hash = "sha256:c3318504d904532cb4673df849cfc222ccbd92915d1697d34c9e9e55f3f5eb53"}, ] [package.dependencies] @@ -947,4 +947,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "7feced3e0ab64b956db1c6eaf112aa2553494bf4a27127a5afd0a8062d952917" +content-hash = "cfe10e9fdb3ea780c9d8fae0f9cf8b92b3a37356be13633f655d910f71e0035e" diff --git a/pyproject.toml b/pyproject.toml index c1df2ec..858d6b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -flagsmith-flag-engine = "^9.1.0" +flagsmith-flag-engine = "^10.0.0" python = ">=3.9,<4" requests = "^2.32.3" requests-futures = "^1.0.1" diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 6d5258c..ce9219b 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -165,7 +165,6 @@ def test_get_identity_flags_uses_local_environment_when_available( "name": "some_feature", "enabled": True, "value": "some-feature-state-value", - "feature_key": "1", "metadata": {"flagsmith_id": 1}, } }, diff --git a/tests/test_models.py b/tests/test_models.py index 35d960d..7cc8283 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -10,7 +10,6 @@ def test_flag_from_evaluation_result() -> None: # Given flag_result: SDKFlagResult = { "enabled": True, - "feature_key": "123", "name": "test_feature", "reason": "DEFAULT", "value": "test-value", @@ -36,7 +35,6 @@ def test_flag_from_evaluation_result() -> None: { "feature1": { "enabled": True, - "feature_key": "1", "name": "feature1", "reason": "DEFAULT", "value": "value1", @@ -49,7 +47,6 @@ def test_flag_from_evaluation_result() -> None: { "feature1": { "enabled": True, - "feature_key": "1", "name": "feature1", "reason": "DEFAULT", "value": "value1", @@ -62,7 +59,6 @@ def test_flag_from_evaluation_result() -> None: { "feature1": { "enabled": True, - "feature_key": "1", "name": "feature1", "reason": "DEFAULT", "value": "value1", @@ -70,7 +66,6 @@ def test_flag_from_evaluation_result() -> None: }, "feature2": { "enabled": True, - "feature_key": "2", "name": "feature2", "reason": "DEFAULT", "value": "value2", @@ -78,7 +73,6 @@ def test_flag_from_evaluation_result() -> None: }, "feature3": { "enabled": True, - "feature_key": "3", "name": "feature3", "reason": "DEFAULT", "value": 42, @@ -130,7 +124,6 @@ def test_flag_from_evaluation_result_value_types( # Given flag_result: SDKFlagResult = { "enabled": True, - "feature_key": "123", "name": "test_feature", "reason": "DEFAULT", "value": value, @@ -148,7 +141,6 @@ def test_flag_from_evaluation_result_missing_metadata__raises_expected() -> None # Given flag_result: SDKFlagResult = { "enabled": True, - "feature_key": "123", "name": "test_feature", "reason": "DEFAULT", "value": "test-value", From 32ff50bed27033bf123b52931392f319acce47e7 Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:42:30 +0100 Subject: [PATCH 102/121] chore(main): release 5.0.0 (#160) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 50af31c..ec4aebc 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.0.1" + ".": "5.0.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ced8d8c..4915f31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## [5.0.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v4.0.1...v5.0.0) (2025-10-24) + +### ⚠ BREAKING CHANGES + +- Restore v3 `OfflineHandler` interface ([#162](https://github.com/Flagsmith/flagsmith-python-client/issues/162)) + +### Features + +- Restore v3 `OfflineHandler` interface ([#162](https://github.com/Flagsmith/flagsmith-python-client/issues/162)) + ([374e292](https://github.com/Flagsmith/flagsmith-python-client/commit/374e29293aca44eafadda672907d9b701b8414fc)) +- Support feature metadata ([#163](https://github.com/Flagsmith/flagsmith-python-client/issues/163)) + ([1bbbdf8](https://github.com/Flagsmith/flagsmith-python-client/commit/1bbbdf8d98054ea4317a1ba3bd95f437a7edbf0e)) +- Support variant priority ([#161](https://github.com/Flagsmith/flagsmith-python-client/issues/161)) + ([4f84044](https://github.com/Flagsmith/flagsmith-python-client/commit/4f84044ea87a9d284f6731ff4cfe4835d5f99fa4)) + +### Bug Fixes + +- `get_identity_segments` tries to return identity override segments + ([#159](https://github.com/Flagsmith/flagsmith-python-client/issues/159)) + ([68d44a1](https://github.com/Flagsmith/flagsmith-python-client/commit/68d44a15feae75905d08103ff8dba53c605377fd)) + +### CI + +- pre-commit autoupdate ([#158](https://github.com/Flagsmith/flagsmith-python-client/issues/158)) + ([e2fe6eb](https://github.com/Flagsmith/flagsmith-python-client/commit/e2fe6eb01ba61a2477d54e75abc636c00c7f1e10)) + +### Dependency Updates + +- Bump `flagsmith-flag-engine` to 10.0.0 ([#165](https://github.com/Flagsmith/flagsmith-python-client/issues/165)) + ([68c40b5](https://github.com/Flagsmith/flagsmith-python-client/commit/68c40b554d4cbc9d7e3b5e31f7238de253648db1)) +- bump urllib3 from 2.2.3 to 2.5.0 ([#157](https://github.com/Flagsmith/flagsmith-python-client/issues/157)) + ([5de9650](https://github.com/Flagsmith/flagsmith-python-client/commit/5de965027d94cd4cd62178615c7e6d14c5340b70)) + ## [4.0.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v4.0.0...v4.0.1) (2025-09-19) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index 858d6b1..95ca3fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "4.0.1" +version = "5.0.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 29dee4de219754ec9874e2db39287f065b2dc166 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 28 Oct 2025 10:33:21 +0000 Subject: [PATCH 103/121] fix: ValueError: Invalid isoformat string on Python 3.10 (#168) --- flagsmith/mappers.py | 5 ++--- flagsmith/utils/datetime.py | 10 ++++++++++ poetry.lock | 15 ++++++++++++++- pyproject.toml | 1 + tests/data/environment.json | 4 ++-- 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 flagsmith/utils/datetime.py diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 7564a89..5e6156d 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -28,6 +28,7 @@ StreamEvent, TraitConfig, ) +from flagsmith.utils.datetime import fromisoformat OverrideKey = typing.Tuple[ int, @@ -69,9 +70,7 @@ def map_sse_event_to_stream_event(event: sseclient.Event) -> StreamEvent: def map_environment_document_to_environment_updated_at( environment_document: dict[str, typing.Any], ) -> datetime: - if ( - updated_at := datetime.fromisoformat(environment_document["updated_at"]) - ).tzinfo is None: + if (updated_at := fromisoformat(environment_document["updated_at"])).tzinfo is None: return updated_at.replace(tzinfo=timezone.utc) return updated_at.astimezone(tz=timezone.utc) diff --git a/flagsmith/utils/datetime.py b/flagsmith/utils/datetime.py new file mode 100644 index 0000000..4df38fb --- /dev/null +++ b/flagsmith/utils/datetime.py @@ -0,0 +1,10 @@ +import sys + +if sys.version_info >= (3, 11): + from datetime import datetime + + fromisoformat = datetime.fromisoformat +else: + import iso8601 + + fromisoformat = iso8601.parse_date diff --git a/poetry.lock b/poetry.lock index 4d9f539..8fa5407 100644 --- a/poetry.lock +++ b/poetry.lock @@ -341,6 +341,19 @@ files = [ {file = "iregexp_check-0.1.4.tar.gz", hash = "sha256:a98e77dd2d9fc91db04f8d9f295f3d69e402813bac5413f22e5866958a902bc1"}, ] +[[package]] +name = "iso8601" +version = "2.1.0" +description = "Simple module to parse ISO 8601 dates" +optional = false +python-versions = ">=3.7,<4.0" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242"}, + {file = "iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df"}, +] + [[package]] name = "jsonpath-rfc9535" version = "0.1.6" @@ -947,4 +960,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "cfe10e9fdb3ea780c9d8fae0f9cf8b92b3a37356be13633f655d910f71e0035e" +content-hash = "22ba0b94f5228702454729e8f3962ecb316d1caf4d69f47df478c04735c67ab2" diff --git a/pyproject.toml b/pyproject.toml index 95ca3fe..f773994 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] flagsmith-flag-engine = "^10.0.0" +iso8601 = { version = "^2.1.0", python = "<3.11" } python = ">=3.9,<4" requests = "^2.32.3" requests-futures = "^1.0.1" diff --git a/tests/data/environment.json b/tests/data/environment.json index ea63981..0075f01 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -54,13 +54,13 @@ "enabled": true } ], - "updated_at": "2023-07-14 16:12:00.000000", + "updated_at": "2023-07-14T16:12:00.000000Z", "identity_overrides": [ { "identifier": "overridden-id", "identity_uuid": "0f21cde8-63c5-4e50-baca-87897fa6cd01", "created_date": "2019-08-27T14:53:45.698555Z", - "updated_at": "2023-07-14 16:12:00.000000", + "updated_at": "2023-07-14T16:12:00.000000Z", "environment_api_key": "B62qaMZNwfiqT76p38ggrQ", "identity_features": [ { From c20f54385390677657edcafc8c38204c5197e736 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 28 Oct 2025 10:41:25 +0000 Subject: [PATCH 104/121] fix: TypeError: 'NoneType' object is not subscriptable (Python 3.11) (#170) --- flagsmith/api/types.py | 2 +- flagsmith/mappers.py | 4 ++-- tests/data/environment.json | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/flagsmith/api/types.py b/flagsmith/api/types.py index fda43fe..eb1b00e 100644 --- a/flagsmith/api/types.py +++ b/flagsmith/api/types.py @@ -49,7 +49,7 @@ class MultivariateFeatureStateValueModel(typing.TypedDict): class FeatureStateModel(typing.TypedDict): enabled: bool - feature_segment: NotRequired[FeatureSegmentModel] + feature_segment: NotRequired[typing.Optional[FeatureSegmentModel]] feature_state_value: object feature: FeatureModel featurestate_uuid: str diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index 5e6156d..e5fff9d 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -266,7 +266,7 @@ def _map_environment_document_feature_states_to_feature_contexts( for multivariate_feature_state_value in multivariate_feature_state_values ] - if "feature_segment" in feature_state: - feature_context["priority"] = feature_state["feature_segment"]["priority"] + if feature_segment := feature_state.get("feature_segment"): + feature_context["priority"] = feature_segment["priority"] yield feature_context diff --git a/tests/data/environment.json b/tests/data/environment.json index 0075f01..6dc7e51 100644 --- a/tests/data/environment.json +++ b/tests/data/environment.json @@ -51,7 +51,8 @@ "id": 1 }, "segment_id": null, - "enabled": true + "enabled": true, + "feature_segment": null } ], "updated_at": "2023-07-14T16:12:00.000000Z", From ab093bd71bf870461a153864eb2b9ea08533f852 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 28 Oct 2025 12:15:28 +0000 Subject: [PATCH 105/121] deps: Bump `flagsmith-flag-engine` to 10.0.1 (#171) --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8fa5407..49ab70a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -259,14 +259,14 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "10.0.0" +version = "10.0.1" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith_flag_engine-10.0.0-py3-none-any.whl", hash = "sha256:da9dec37556416a6bec2004ec6dbe7e2581dfdd24167fe224a2280db7f01c011"}, - {file = "flagsmith_flag_engine-10.0.0.tar.gz", hash = "sha256:c3318504d904532cb4673df849cfc222ccbd92915d1697d34c9e9e55f3f5eb53"}, + {file = "flagsmith_flag_engine-10.0.1-py3-none-any.whl", hash = "sha256:f1b586ec2892492a99a525dc65a3e446e11ad7cb88fa7b7cfd36487dc8494280"}, + {file = "flagsmith_flag_engine-10.0.1.tar.gz", hash = "sha256:c5c29e59535593cdd5a7c2732061a5e71d114c20a5063197077490020572349f"}, ] [package.dependencies] @@ -960,4 +960,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "22ba0b94f5228702454729e8f3962ecb316d1caf4d69f47df478c04735c67ab2" +content-hash = "6665e4f72b0b6bf0c95cec94689e86b852cd0579035bb3031daddeff0aba8161" diff --git a/pyproject.toml b/pyproject.toml index f773994..bdcda7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -flagsmith-flag-engine = "^10.0.0" +flagsmith-flag-engine = "^10.0.1" iso8601 = { version = "^2.1.0", python = "<3.11" } python = ">=3.9,<4" requests = "^2.32.3" From 2555c831a77dcf0cd0cb0497699fcc6f226726c2 Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:17:30 +0000 Subject: [PATCH 106/121] chore(main): release 5.0.1 (#169) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ec4aebc..a37f3a8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.0.0" + ".": "5.0.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4915f31..e6e7dae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [5.0.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.0...v5.0.1) (2025-10-28) + +### Bug Fixes + +- TypeError: 'NoneType' object is not subscriptable (Python 3.11) + ([#170](https://github.com/Flagsmith/flagsmith-python-client/issues/170)) + ([c20f543](https://github.com/Flagsmith/flagsmith-python-client/commit/c20f54385390677657edcafc8c38204c5197e736)) +- ValueError: Invalid isoformat string on Python 3.10 + ([#168](https://github.com/Flagsmith/flagsmith-python-client/issues/168)) + ([29dee4d](https://github.com/Flagsmith/flagsmith-python-client/commit/29dee4de219754ec9874e2db39287f065b2dc166)) + +### Dependency Updates + +- Bump `flagsmith-flag-engine` to 10.0.1 ([#171](https://github.com/Flagsmith/flagsmith-python-client/issues/171)) + ([ab093bd](https://github.com/Flagsmith/flagsmith-python-client/commit/ab093bd71bf870461a153864eb2b9ea08533f852)) + ## [5.0.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v4.0.1...v5.0.0) (2025-10-24) ### ⚠ BREAKING CHANGES diff --git a/pyproject.toml b/pyproject.toml index bdcda7d..fbac972 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "5.0.0" +version = "5.0.1" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 9a0fc58dc4e7eb590271a76e62a29078cf54b01b Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 30 Oct 2025 14:18:23 +0000 Subject: [PATCH 107/121] deps: Bump `flagsmith-flag-engine` to 10.0.2 (#173) --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 49ab70a..a869710 100644 --- a/poetry.lock +++ b/poetry.lock @@ -259,14 +259,14 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "10.0.1" +version = "10.0.2" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith_flag_engine-10.0.1-py3-none-any.whl", hash = "sha256:f1b586ec2892492a99a525dc65a3e446e11ad7cb88fa7b7cfd36487dc8494280"}, - {file = "flagsmith_flag_engine-10.0.1.tar.gz", hash = "sha256:c5c29e59535593cdd5a7c2732061a5e71d114c20a5063197077490020572349f"}, + {file = "flagsmith_flag_engine-10.0.2-py3-none-any.whl", hash = "sha256:5df2f34f5f0b9b1aa1ada21f59a08ecc9d0fd32cd6357e5af6493cb98ac0beb7"}, + {file = "flagsmith_flag_engine-10.0.2.tar.gz", hash = "sha256:312a1cb5b7396deb6e2e32251a58e8c6f65dfaca1872f305d0e2379a52738134"}, ] [package.dependencies] @@ -960,4 +960,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "6665e4f72b0b6bf0c95cec94689e86b852cd0579035bb3031daddeff0aba8161" +content-hash = "73cee378ef22196323becc942d6d95836a33198191008750680d9a6214168621" diff --git a/pyproject.toml b/pyproject.toml index fbac972..df0d33e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -flagsmith-flag-engine = "^10.0.1" +flagsmith-flag-engine = "^10.0.2" iso8601 = { version = "^2.1.0", python = "<3.11" } python = ">=3.9,<4" requests = "^2.32.3" From ec4bdb775fa51e3ff131c9dabde59d81b8e5e793 Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:42:31 +0000 Subject: [PATCH 108/121] chore(main): release 5.0.2 (#174) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a37f3a8..fc4166e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.0.1" + ".": "5.0.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e7dae..b5be8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [5.0.2](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.1...v5.0.2) (2025-10-30) + +### Dependency Updates + +- Bump `flagsmith-flag-engine` to 10.0.2 ([#173](https://github.com/Flagsmith/flagsmith-python-client/issues/173)) + ([9a0fc58](https://github.com/Flagsmith/flagsmith-python-client/commit/9a0fc58dc4e7eb590271a76e62a29078cf54b01b)) + ## [5.0.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.0...v5.0.1) (2025-10-28) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index df0d33e..9d7b897 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "5.0.1" +version = "5.0.2" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From c0d57ec4cd208b79908298e6cf9a3504e5d42fda Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:13:39 +0000 Subject: [PATCH 109/121] chore: Standardize engine metadata (#177) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: khvn26 <979078+khvn26@users.noreply.github.com> --- flagsmith/mappers.py | 12 ++++++------ flagsmith/models.py | 2 +- flagsmith/types.py | 4 ++-- tests/test_flagsmith.py | 2 +- tests/test_models.py | 14 +++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/flagsmith/mappers.py b/flagsmith/mappers.py index e5fff9d..18d1acf 100644 --- a/flagsmith/mappers.py +++ b/flagsmith/mappers.py @@ -46,11 +46,11 @@ def map_segment_results_to_identity_segments( for segment_result in segment_results: if metadata := segment_result.get("metadata"): if metadata.get("source") == "api" and ( - (flagsmith_id := metadata.get("flagsmith_id")) is not None + (segment_id := metadata.get("id")) is not None ): identity_segments.append( Segment( - id=flagsmith_id, + id=segment_id, name=segment_result["name"], ) ) @@ -132,7 +132,7 @@ def map_environment_document_to_context( ) ), "metadata": SegmentMetadata( - flagsmith_id=segment_id, + id=segment_id, source="api", ), } @@ -202,9 +202,9 @@ def _map_identity_overrides_to_segments( "enabled": feature_enabled, "value": feature_value, "priority": float("-inf"), # Highest possible priority - "metadata": {"flagsmith_id": flagsmith_id}, + "metadata": {"id": feature_id}, } - for flagsmith_id, feature_name, feature_enabled, feature_value in overrides_key + for feature_id, feature_name, feature_enabled, feature_value in overrides_key ], metadata=SegmentMetadata(source="identity_overrides"), ) @@ -237,7 +237,7 @@ def _map_environment_document_feature_states_to_feature_contexts( feature_states: list[FeatureStateModel], ) -> typing.Iterable[FeatureContext[FeatureMetadata]]: for feature_state in feature_states: - metadata: FeatureMetadata = {"flagsmith_id": feature_state["feature"]["id"]} + metadata: FeatureMetadata = {"id": feature_state["feature"]["id"]} feature_context = FeatureContext[FeatureMetadata]( key=str( feature_state.get("django_id") or feature_state["featurestate_uuid"] diff --git a/flagsmith/models.py b/flagsmith/models.py index ab69d2d..72beb25 100644 --- a/flagsmith/models.py +++ b/flagsmith/models.py @@ -35,7 +35,7 @@ def from_evaluation_result( enabled=flag_result["enabled"], value=flag_result["value"], feature_name=flag_result["name"], - feature_id=metadata["flagsmith_id"], + feature_id=metadata["id"], ) raise ValueError( "FlagResult metadata is missing. Cannot create Flag instance. " diff --git a/flagsmith/types.py b/flagsmith/types.py index 668d9df..8a50741 100644 --- a/flagsmith/types.py +++ b/flagsmith/types.py @@ -38,14 +38,14 @@ class ApplicationMetadata(typing.TypedDict): class SegmentMetadata(typing.TypedDict): - flagsmith_id: NotRequired[int] + id: NotRequired[int] """The ID of the segment used in Flagsmith API.""" source: NotRequired[typing.Literal["api", "identity_overrides"]] """The source of the segment, e.g. 'api', 'identity_overrides'.""" class FeatureMetadata(typing.TypedDict): - flagsmith_id: int + id: int """The ID of the feature used in Flagsmith API.""" diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index ce9219b..cf1367a 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -165,7 +165,7 @@ def test_get_identity_flags_uses_local_environment_when_available( "name": "some_feature", "enabled": True, "value": "some-feature-state-value", - "metadata": {"flagsmith_id": 1}, + "metadata": {"id": 1}, } }, "segments": [], diff --git a/tests/test_models.py b/tests/test_models.py index 7cc8283..c992395 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -13,7 +13,7 @@ def test_flag_from_evaluation_result() -> None: "name": "test_feature", "reason": "DEFAULT", "value": "test-value", - "metadata": {"flagsmith_id": 123}, + "metadata": {"id": 123}, } # When @@ -38,7 +38,7 @@ def test_flag_from_evaluation_result() -> None: "name": "feature1", "reason": "DEFAULT", "value": "value1", - "metadata": {"flagsmith_id": 1}, + "metadata": {"id": 1}, } }, ["feature1"], @@ -50,7 +50,7 @@ def test_flag_from_evaluation_result() -> None: "name": "feature1", "reason": "DEFAULT", "value": "value1", - "metadata": {"flagsmith_id": 1}, + "metadata": {"id": 1}, } }, ["feature1"], @@ -62,21 +62,21 @@ def test_flag_from_evaluation_result() -> None: "name": "feature1", "reason": "DEFAULT", "value": "value1", - "metadata": {"flagsmith_id": 1}, + "metadata": {"id": 1}, }, "feature2": { "enabled": True, "name": "feature2", "reason": "DEFAULT", "value": "value2", - "metadata": {"flagsmith_id": 2}, + "metadata": {"id": 2}, }, "feature3": { "enabled": True, "name": "feature3", "reason": "DEFAULT", "value": 42, - "metadata": {"flagsmith_id": 3}, + "metadata": {"id": 3}, }, }, ["feature1", "feature2", "feature3"], @@ -127,7 +127,7 @@ def test_flag_from_evaluation_result_value_types( "name": "test_feature", "reason": "DEFAULT", "value": value, - "metadata": {"flagsmith_id": 123}, + "metadata": {"id": 123}, } # When From 470c4e3e71be55795387ab023b4fe6f7623d6aec Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:28:20 +0000 Subject: [PATCH 110/121] fix: `get_environment_flags` includes segments in evaluation context (#179) --- flagsmith/flagsmith.py | 9 +++++- tests/test_flagsmith.py | 72 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index 8eaf847..15da5ec 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -332,7 +332,14 @@ def _get_environment_flags_from_document(self) -> Flags: if self._evaluation_context is None: raise TypeError("No environment present") - evaluation_result = engine.get_evaluation_result(self._evaluation_context) + # Omit segments from evaluation context for environment flags + # as they are only relevant for identity-specific evaluations + context_without_segments = self._evaluation_context.copy() + context_without_segments.pop("segments", None) + + evaluation_result = engine.get_evaluation_result( + context=context_without_segments, + ) return Flags.from_evaluation_result( evaluation_result=evaluation_result, diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index cf1367a..494e854 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -94,6 +94,41 @@ def test_get_environment_flags_uses_local_environment_when_available( assert all_flags[0].value == "some-value" +def test_get_environment_flags_omits_segments_from_evaluation_context( + mocker: MockerFixture, + local_eval_flagsmith: Flagsmith, + evaluation_context: SDKEvaluationContext, +) -> None: + # Given + mock_get_evaluation_result = mocker.patch( + "flagsmith.flagsmith.engine.get_evaluation_result", + autospec=True, + ) + + expected_evaluation_result = { + "flags": { + "some_feature": { + "name": "some_feature", + "enabled": True, + "value": "some-feature-state-value", + "metadata": {"id": 1}, + } + }, + "segments": [], + } + + mock_get_evaluation_result.return_value = expected_evaluation_result + + # When + local_eval_flagsmith.get_environment_flags() + + # Then + # Verify segments are not present in the context passed to the engine + context_without_segments = evaluation_context.copy() + context_without_segments.pop("segments", None) + mock_get_evaluation_result.assert_called_once_with(context=context_without_segments) + + @responses.activate() def test_get_identity_flags_calls_api_when_no_local_environment_no_traits( flagsmith: Flagsmith, identities_json: str @@ -191,6 +226,43 @@ def test_get_identity_flags_uses_local_environment_when_available( assert identity_flags[0].value == "some-feature-state-value" +def test_get_identity_flags_includes_segments_in_evaluation_context( + mocker: MockerFixture, + local_eval_flagsmith: Flagsmith, +) -> None: + # Given + mock_get_evaluation_result = mocker.patch( + "flagsmith.flagsmith.engine.get_evaluation_result", + autospec=True, + ) + + expected_evaluation_result = { + "flags": { + "some_feature": { + "name": "some_feature", + "enabled": True, + "value": "some-feature-state-value", + "metadata": {"id": 1}, + } + }, + "segments": [], + } + + identifier = "identifier" + traits = {"some_trait": "some_value"} + + mock_get_evaluation_result.return_value = expected_evaluation_result + + # When + local_eval_flagsmith.get_identity_flags(identifier, traits) + + # Then + # Verify segments are present in the context passed to the engine for identity flags + call_args = mock_get_evaluation_result.call_args + context = call_args[1]["context"] + assert "segments" in context + + @responses.activate() def test_get_identity_flags__transient_identity__calls_expected( flagsmith: Flagsmith, From cf54be555b3d3178cd1494e8fc775b0283ad1ee9 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 25 Nov 2025 12:08:15 +0000 Subject: [PATCH 111/121] deps: Bump `flagsmith-flag-engine` to 10.0.3 (#182) --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index a869710..0f9fff4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "certifi" @@ -259,14 +259,14 @@ files = [ [[package]] name = "flagsmith-flag-engine" -version = "10.0.2" +version = "10.0.3" description = "Flag engine for the Flagsmith API." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "flagsmith_flag_engine-10.0.2-py3-none-any.whl", hash = "sha256:5df2f34f5f0b9b1aa1ada21f59a08ecc9d0fd32cd6357e5af6493cb98ac0beb7"}, - {file = "flagsmith_flag_engine-10.0.2.tar.gz", hash = "sha256:312a1cb5b7396deb6e2e32251a58e8c6f65dfaca1872f305d0e2379a52738134"}, + {file = "flagsmith_flag_engine-10.0.3-py3-none-any.whl", hash = "sha256:aed9009377fc1a6322483277f971f06d542668a69d93cbe4a3efd4baae78dfc1"}, + {file = "flagsmith_flag_engine-10.0.3.tar.gz", hash = "sha256:0aa449bb87bee54fc67b5c7ca25eca78246a7bbb5a6cc229260c3f262d58ac54"}, ] [package.dependencies] @@ -960,4 +960,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "73cee378ef22196323becc942d6d95836a33198191008750680d9a6214168621" +content-hash = "1a65acfb68f8c7f4226460c21adbcbb27a105635cb8287f6bbfc5aa9c900c5dd" diff --git a/pyproject.toml b/pyproject.toml index 9d7b897..0e6a167 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ documentation = "https://docs.flagsmith.com" packages = [{ include = "flagsmith" }] [tool.poetry.dependencies] -flagsmith-flag-engine = "^10.0.2" +flagsmith-flag-engine = "^10.0.3" iso8601 = { version = "^2.1.0", python = "<3.11" } python = ">=3.9,<4" requests = "^2.32.3" From 7996c7b28aee02d8c6f5b36dd399a7a3a5018362 Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:28:47 +0000 Subject: [PATCH 112/121] chore(main): release 5.0.3 (#178) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 18 ++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fc4166e..938e9bf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.0.2" + ".": "5.0.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b5be8c4..c58c3fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [5.0.3](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.2...v5.0.3) (2025-11-25) + +### Bug Fixes + +- `get_environment_flags` includes segments in evaluation context + ([#179](https://github.com/Flagsmith/flagsmith-python-client/issues/179)) + ([470c4e3](https://github.com/Flagsmith/flagsmith-python-client/commit/470c4e3e71be55795387ab023b4fe6f7623d6aec)) + +### Dependency Updates + +- Bump `flagsmith-flag-engine` to 10.0.3 ([#182](https://github.com/Flagsmith/flagsmith-python-client/issues/182)) + ([cf54be5](https://github.com/Flagsmith/flagsmith-python-client/commit/cf54be555b3d3178cd1494e8fc775b0283ad1ee9)) + +### Other + +- Standardize engine metadata ([#177](https://github.com/Flagsmith/flagsmith-python-client/issues/177)) + ([c0d57ec](https://github.com/Flagsmith/flagsmith-python-client/commit/c0d57ec4cd208b79908298e6cf9a3504e5d42fda)) + ## [5.0.2](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.1...v5.0.2) (2025-10-30) ### Dependency Updates diff --git a/pyproject.toml b/pyproject.toml index 0e6a167..c4b405d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "5.0.2" +version = "5.0.3" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From cafb3e209491afa66f9b84e6b8aeb81c2b220a07 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:19:59 +0000 Subject: [PATCH 113/121] ci: pre-commit autoupdate (#180) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d310844..ff76284 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.9.0 + rev: 25.11.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 From 5c865cefd90d0ab8dc139e5d608809c3d03105a7 Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Mon, 5 Jan 2026 16:39:19 +0100 Subject: [PATCH 114/121] feat: added-trait-model-type (#186) --- flagsmith/api/types.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flagsmith/api/types.py b/flagsmith/api/types.py index eb1b00e..c71f46e 100644 --- a/flagsmith/api/types.py +++ b/flagsmith/api/types.py @@ -1,5 +1,6 @@ import typing +from flag_engine.engine import ContextValue from flag_engine.segments.types import ConditionOperator, RuleType from typing_extensions import NotRequired @@ -67,3 +68,9 @@ class EnvironmentModel(typing.TypedDict): identity_overrides: list[IdentityModel] name: str project: ProjectModel + + +class TraitModel(typing.TypedDict): + trait_key: str + trait_value: ContextValue + transient: NotRequired[bool] From ef13716013313ab5eaaaf4de3ca577707ea82a75 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:40:47 +0000 Subject: [PATCH 115/121] ci: pre-commit autoupdate (#185) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff76284..3680bcf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.11.0 + rev: 25.12.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 From efd43fd01d0b591ee3583a3ebf13846cc129f5e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:42:17 +0000 Subject: [PATCH 116/121] chore(deps): bump urllib3 from 2.5.0 to 2.6.0 (#184) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0f9fff4..24b894c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -919,21 +919,21 @@ files = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f"}, + {file = "urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" From d18c71ecd0ca88ea682aa8638b368a3de7a1bf7e Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:45:30 +0000 Subject: [PATCH 117/121] chore(main): release 5.1.0 (#183) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 938e9bf..c8f5b36 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.0.3" + ".": "5.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index c58c3fc..aa056b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [5.1.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.3...v5.1.0) (2026-01-05) + +### Features + +- added-trait-model-type ([#186](https://github.com/Flagsmith/flagsmith-python-client/issues/186)) + ([5c865ce](https://github.com/Flagsmith/flagsmith-python-client/commit/5c865cefd90d0ab8dc139e5d608809c3d03105a7)) + +### CI + +- pre-commit autoupdate ([#180](https://github.com/Flagsmith/flagsmith-python-client/issues/180)) + ([cafb3e2](https://github.com/Flagsmith/flagsmith-python-client/commit/cafb3e209491afa66f9b84e6b8aeb81c2b220a07)) +- pre-commit autoupdate ([#185](https://github.com/Flagsmith/flagsmith-python-client/issues/185)) + ([ef13716](https://github.com/Flagsmith/flagsmith-python-client/commit/ef13716013313ab5eaaaf4de3ca577707ea82a75)) + +### Other + +- **deps:** bump urllib3 from 2.5.0 to 2.6.0 ([#184](https://github.com/Flagsmith/flagsmith-python-client/issues/184)) + ([efd43fd](https://github.com/Flagsmith/flagsmith-python-client/commit/efd43fd01d0b591ee3583a3ebf13846cc129f5e9)) + ## [5.0.3](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.2...v5.0.3) (2025-11-25) ### Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index c4b405d..762de5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "5.0.3" +version = "5.1.0" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From a636536162f94589bd7337090dad493fb3745ef0 Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Mon, 5 Jan 2026 17:39:54 +0100 Subject: [PATCH 118/121] fix: use-typing-extension (#188) --- flagsmith/api/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flagsmith/api/types.py b/flagsmith/api/types.py index c71f46e..3656487 100644 --- a/flagsmith/api/types.py +++ b/flagsmith/api/types.py @@ -2,7 +2,7 @@ from flag_engine.engine import ContextValue from flag_engine.segments.types import ConditionOperator, RuleType -from typing_extensions import NotRequired +from typing_extensions import NotRequired, TypedDict class SegmentConditionModel(typing.TypedDict): @@ -70,7 +70,7 @@ class EnvironmentModel(typing.TypedDict): project: ProjectModel -class TraitModel(typing.TypedDict): +class TraitModel(TypedDict): trait_key: str trait_value: ContextValue transient: NotRequired[bool] From af4f239f06e9ce8d811cab6462d543c7a4cfe43e Mon Sep 17 00:00:00 2001 From: Flagsmith Bot <65724737+flagsmithdev@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:43:04 +0000 Subject: [PATCH 119/121] chore(main): release 5.1.1 (#189) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c8f5b36..3229161 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.1.0" + ".": "5.1.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index aa056b3..d1e93a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [5.1.1](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.1.0...v5.1.1) (2026-01-05) + +### Bug Fixes + +- use-typing-extension ([#188](https://github.com/Flagsmith/flagsmith-python-client/issues/188)) + ([a636536](https://github.com/Flagsmith/flagsmith-python-client/commit/a636536162f94589bd7337090dad493fb3745ef0)) + ## [5.1.0](https://github.com/Flagsmith/flagsmith-python-client/compare/v5.0.3...v5.1.0) (2026-01-05) ### Features diff --git a/pyproject.toml b/pyproject.toml index 762de5a..c059862 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "flagsmith" -version = "5.1.0" +version = "5.1.1" description = "Flagsmith Python SDK" authors = ["Flagsmith "] license = "BSD3" From 0aa423183e6e665e0532a85019fbb40e625b1b5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 08:52:10 +0530 Subject: [PATCH 120/121] chore(deps-dev): bump virtualenv from 20.34.0 to 20.36.1 (#191) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 24b894c..6be6213 100644 --- a/poetry.lock +++ b/poetry.lock @@ -252,11 +252,25 @@ description = "A platform independent file lock." optional = false python-versions = ">=3.9" groups = ["dev"] +markers = "python_version == \"3.9\"" files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] +[[package]] +name = "filelock" +version = "3.20.3" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, + {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, +] + [[package]] name = "flagsmith-flag-engine" version = "10.0.3" @@ -937,19 +951,22 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "virtualenv" -version = "20.34.0" +version = "20.36.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026"}, - {file = "virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a"}, + {file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, + {file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, ] [package.dependencies] distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" +filelock = [ + {version = ">=3.16.1,<4", markers = "python_version < \"3.10\""}, + {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""}, +] platformdirs = ">=3.9.1,<5" typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} From ccced7f0f36dadba1a4d75d8aa363d23be1bfea8 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Sat, 7 Feb 2026 08:20:40 +0000 Subject: [PATCH 121/121] chore: Remove amannn/action-semantic-pull-request workflow (#194) --- .github/workflows/pull-request.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/workflows/pull-request.yml diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml deleted file mode 100644 index 059f7d0..0000000 --- a/.github/workflows/pull-request.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Pull Request - -on: - pull_request: - -jobs: - conventional-commit: - name: Conventional Commit - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Check PR Conventional Commit title - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - types: | # mirrors changelog-sections in the /release-please-config.json - feat - fix - ci - docs - deps - refactor - test - chore