From 307a36de16ba2f0db42f5100d2560cc386e9c70d Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:07:46 +0100 Subject: [PATCH 01/13] Update for pydantic v2 compatibility --- README.md | 9 +- readme_example.py | 9 +- requirements.txt | 2 +- tests/conftest.py | 10 +- .../api/provider/achievements/models.py | 14 +- xbox/webapi/api/provider/catalog/models.py | 351 +++++++++--------- xbox/webapi/api/provider/cqs/models.py | 2 +- xbox/webapi/api/provider/gameclips/models.py | 2 +- xbox/webapi/api/provider/lists/models.py | 6 +- xbox/webapi/api/provider/mediahub/models.py | 8 +- xbox/webapi/api/provider/message/models.py | 18 +- xbox/webapi/api/provider/people/models.py | 102 ++--- xbox/webapi/api/provider/presence/__init__.py | 4 +- xbox/webapi/api/provider/presence/models.py | 31 +- .../webapi/api/provider/screenshots/models.py | 4 +- xbox/webapi/api/provider/smartglass/models.py | 44 +-- xbox/webapi/api/provider/titlehub/models.py | 48 +-- xbox/webapi/api/provider/userstats/models.py | 18 +- xbox/webapi/authentication/models.py | 18 +- xbox/webapi/authentication/xal.py | 2 +- xbox/webapi/common/filetimes.py | 14 +- xbox/webapi/common/models.py | 17 +- xbox/webapi/common/request_signer.py | 4 +- xbox/webapi/scripts/authenticate.py | 2 +- xbox/webapi/scripts/change_gamertag.py | 2 +- xbox/webapi/scripts/friends.py | 2 +- 26 files changed, 372 insertions(+), 371 deletions(-) diff --git a/README.md b/README.md index eaf0266d..e425ceb4 100644 --- a/README.md +++ b/README.md @@ -100,12 +100,17 @@ async def async_main(): with open(tokens_file) as f: tokens = f.read() # Assign gathered tokens - auth_mgr.oauth = OAuth2TokenResponse.parse_raw(tokens) + auth_mgr.oauth = OAuth2TokenResponse.model_validate_json(tokens) except FileNotFoundError as e: print( f"File {tokens_file} isn`t found or it doesn`t contain tokens! err={e}" ) - sys.exit(-1) + print("Authorizing via OAUTH") + url = auth_mgr.generate_authorization_url() + print(f"Auth via URL: {url}") + authorization_code = input("Enter authorization code> ") + tokens = await auth_mgr.request_oauth_token(authorization_code) + auth_mgr.oauth = tokens """ Refresh tokens, just in case diff --git a/readme_example.py b/readme_example.py index db8cebfe..af07cc1b 100644 --- a/readme_example.py +++ b/readme_example.py @@ -39,12 +39,17 @@ async def async_main(): with open(tokens_file) as f: tokens = f.read() # Assign gathered tokens - auth_mgr.oauth = OAuth2TokenResponse.parse_raw(tokens) + auth_mgr.oauth = OAuth2TokenResponse.model_validate_json(tokens) except FileNotFoundError as e: print( f"File {tokens_file} isn`t found or it doesn`t contain tokens! err={e}" ) - sys.exit(-1) + print("Authorizing via OAUTH") + url = auth_mgr.generate_authorization_url() + print(f"Auth via URL: {url}") + authorization_code = input("Enter authorization code> ") + tokens = await auth_mgr.request_oauth_token(authorization_code) + auth_mgr.oauth = tokens """ Refresh tokens, just in case diff --git a/requirements.txt b/requirements.txt index 95d56ac8..e5ce1b2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ httpx appdirs ecdsa ms_cv -pydantic +pydantic==2.* # Dev pytest diff --git a/tests/conftest.py b/tests/conftest.py index 7d6d40cc..560e665b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, UTC import uuid from ecdsa.keys import SigningKey, VerifyingKey @@ -29,9 +29,9 @@ async def auth_mgr(event_loop): session = SignedSession() mgr = AuthenticationManager(session, "abc", "123", "http://localhost") - mgr.oauth = OAuth2TokenResponse.parse_raw(get_response("auth_oauth2_token")) - mgr.user_token = XAUResponse.parse_raw(get_response("auth_user_token")) - mgr.xsts_token = XSTSResponse.parse_raw(get_response("auth_xsts_token")) + mgr.oauth = OAuth2TokenResponse.model_validate_json(get_response("auth_oauth2_token")) + mgr.user_token = XAUResponse.model_validate_json(get_response("auth_user_token")) + mgr.xsts_token = XSTSResponse.model_validate_json(get_response("auth_xsts_token")) yield mgr await session.aclose() @@ -77,4 +77,4 @@ def synthetic_request_signer(ecdsa_signing_key) -> RequestSigner: @pytest.fixture(scope="session") def synthetic_timestamp() -> datetime: - return datetime.utcfromtimestamp(1586999965) + return datetime.fromtimestamp(1586999965, UTC) diff --git a/xbox/webapi/api/provider/achievements/models.py b/xbox/webapi/api/provider/achievements/models.py index 554be841..99e5406a 100644 --- a/xbox/webapi/api/provider/achievements/models.py +++ b/xbox/webapi/api/provider/achievements/models.py @@ -7,7 +7,7 @@ class PagingInfo(CamelCaseModel): - continuation_token: Optional[str] + continuation_token: Optional[str] = None total_records: int @@ -62,7 +62,7 @@ class TitleAssociation(BaseModel): class Requirement(CamelCaseModel): id: str - current: Optional[str] + current: Optional[str] = None target: str operation_type: str value_type: str @@ -81,11 +81,11 @@ class MediaAsset(BaseModel): class Reward(CamelCaseModel): - name: Any - description: Any + name: Any = None + description: Any = None value: str type: str - media_asset: Any + media_asset: Any = None value_type: str @@ -104,10 +104,10 @@ class Achievement(CamelCaseModel): product_id: str achievement_type: str participation_type: str - time_window: Any + time_window: Any = None rewards: List[Reward] estimated_time: time - deeplink: Any + deeplink: Any = None is_revoked: bool diff --git a/xbox/webapi/api/provider/catalog/models.py b/xbox/webapi/api/provider/catalog/models.py index 5386ddb6..187290db 100644 --- a/xbox/webapi/api/provider/catalog/models.py +++ b/xbox/webapi/api/provider/catalog/models.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Any, List, Optional, Union -from pydantic import Field, validator +from pydantic import Field, field_validator from xbox.webapi.common.models import PascalCaseModel @@ -24,16 +24,16 @@ class PlatformType(str, Enum): class Image(PascalCaseModel): - file_id: Optional[str] - eis_listing_identifier: Any = Field(alias="EISListingIdentifier") - background_color: Optional[str] - caption: Optional[str] + file_id: Optional[str] = None + eis_listing_identifier: Any = Field(None, alias="EISListingIdentifier") + background_color: Optional[str] = None + caption: Optional[str] = None file_size_in_bytes: int - foreground_color: Optional[str] + foreground_color: Optional[str] = None height: int - image_position_info: Optional[str] + image_position_info: Optional[str] = None image_purpose: str - unscaled_image_sha256_hash: Optional[str] = Field(alias="UnscaledImageSHA256Hash") + unscaled_image_sha256_hash: Optional[str] = Field(None, alias="UnscaledImageSHA256Hash") uri: str width: int @@ -62,47 +62,47 @@ class ContentRating(PascalCaseModel): rating_id: str rating_descriptors: List[str] rating_disclaimers: List - interactive_elements: Optional[List] + interactive_elements: Optional[List] = None class UsageData(PascalCaseModel): aggregate_time_span: str average_rating: float - play_count: Optional[int] + play_count: Optional[int] = None rating_count: int - rental_count: Optional[str] - trial_count: Optional[str] - purchase_count: Optional[str] + rental_count: Optional[str] = None + trial_count: Optional[str] = None + purchase_count: Optional[str] = None class ProductProperties(PascalCaseModel): - attributes: Optional[List] - can_install_to_sd_card: Optional[bool] = Field(alias="CanInstallToSDCard") - category: Optional[str] - sub_category: Optional[str] - categories: Optional[List[str]] - extensions: Any - is_accessible: Optional[bool] - is_line_of_business_app: Optional[bool] - is_published_to_legacy_windows_phone_store: Optional[bool] - is_published_to_legacy_windows_store: Optional[bool] - is_settings_app: Optional[bool] - package_family_name: Optional[str] - package_identity_name: Optional[str] - publisher_certificate_name: Optional[str] + attributes: Optional[List] = None + can_install_to_sd_card: Optional[bool] = Field(None, alias="CanInstallToSDCard") + category: Optional[str] = None + sub_category: Optional[str] = None + categories: Optional[List[str]] = None + extensions: Any = None + is_accessible: Optional[bool] = None + is_line_of_business_app: Optional[bool] = None + is_published_to_legacy_windows_phone_store: Optional[bool] = None + is_published_to_legacy_windows_store: Optional[bool] = None + is_settings_app: Optional[bool] = None + package_family_name: Optional[str] = None + package_identity_name: Optional[str] = None + publisher_certificate_name: Optional[str] = None publisher_id: str - xbox_live_tier: Any - xbox_xpa: Any = Field(alias="XboxXPA") - xbox_cross_gen_set_id: Any - xbox_console_gen_optimized: Any - xbox_console_gen_compatible: Any - xbox_live_gold_required: Optional[bool] - ownership_type: Any - pdp_background_color: Optional[str] - has_add_ons: Optional[bool] + xbox_live_tier: Any = None + xbox_xpa: Any = Field(None, alias="XboxXPA") + xbox_cross_gen_set_id: Any = None + xbox_console_gen_optimized: Any = None + xbox_console_gen_compatible: Any = None + xbox_live_gold_required: Optional[bool] = None + ownership_type: Any = None + pdp_background_color: Optional[str] = None + has_add_ons: Optional[bool] = None revision_id: str - product_group_id: Optional[str] - product_group_name: Optional[str] + product_group_id: Optional[str] = None + product_group_name: Optional[str] = None class AlternateId(PascalCaseModel): @@ -113,26 +113,26 @@ class AlternateId(PascalCaseModel): class ValidationData(PascalCaseModel): passed_validation: bool revision_id: str - validation_result_uri: Optional[str] + validation_result_uri: Optional[str] = None class FulfillmentData(PascalCaseModel): product_id: str - wu_bundle_id: Optional[str] + wu_bundle_id: Optional[str] = None wu_category_id: str package_family_name: str sku_id: str - content: Any - package_features: Any + content: Any = None + package_features: Any = None class HardwareProperties(PascalCaseModel): minimum_hardware: List recommended_hardware: List - minimum_processor: Any - recommended_processor: Any - minimum_graphics: Any - recommended_graphics: Any + minimum_processor: Any = None + recommended_processor: Any = None + minimum_graphics: Any = None + recommended_graphics: Any = None class Application(PascalCaseModel): @@ -148,42 +148,42 @@ class FrameworkDependency(PascalCaseModel): class PlatformDependency(PascalCaseModel): - max_tested: Optional[int] - min_version: Optional[int] + max_tested: Optional[int] = None + min_version: Optional[int] = None platform_name: str class Package(PascalCaseModel): - applications: Optional[List[Application]] + applications: Optional[List[Application]] = None architectures: List[str] - capabilities: Optional[List[str]] - device_capabilities: Optional[List[str]] - experience_ids: Optional[List] - framework_dependencies: Optional[List[FrameworkDependency]] - hardware_dependencies: Optional[List] - hardware_requirements: Optional[List] - hash: Optional[str] - hash_algorithm: Optional[str] - is_streaming_app: Optional[bool] - languages: Optional[List[str]] + capabilities: Optional[List[str]] = None + device_capabilities: Optional[List[str]] = None + experience_ids: Optional[List] = None + framework_dependencies: Optional[List[FrameworkDependency]] = None + hardware_dependencies: Optional[List] = None + hardware_requirements: Optional[List] = None + hash: Optional[str] = None + hash_algorithm: Optional[str] = None + is_streaming_app: Optional[bool] = None + languages: Optional[List[str]] = None max_download_size_in_bytes: int - max_install_size_in_bytes: Optional[int] + max_install_size_in_bytes: Optional[int] = None package_format: str - package_family_name: Optional[str] - main_package_family_name_for_dlc: Any - package_full_name: Optional[str] + package_family_name: Optional[str] = None + main_package_family_name_for_dlc: Any = None + package_full_name: Optional[str] = None package_id: str content_id: str - key_id: Optional[str] - package_rank: Optional[int] - package_uri: Optional[str] - platform_dependencies: Optional[List[PlatformDependency]] - platform_dependency_xml_blob: Optional[str] - resource_id: Optional[str] - version: Optional[str] - package_download_uris: Any - driver_dependencies: Optional[List] - fulfillment_data: Optional[FulfillmentData] + key_id: Optional[str] = None + package_rank: Optional[int] = None + package_uri: Optional[str] = None + platform_dependencies: Optional[List[PlatformDependency]] = None + platform_dependency_xml_blob: Optional[str] = None + resource_id: Optional[str] = None + version: Optional[str] = None + package_download_uris: Any = None + driver_dependencies: Optional[List] = None + fulfillment_data: Optional[FulfillmentData] = None class LegalText(PascalCaseModel): @@ -197,60 +197,59 @@ class LegalText(PascalCaseModel): class SkuLocalizedProperty(PascalCaseModel): - contributors: Optional[List] - features: Optional[List] - minimum_notes: Optional[str] - recommended_notes: Optional[str] - release_notes: Optional[str] - display_platform_properties: Any + contributors: Optional[List] = None + features: Optional[List] = None + minimum_notes: Optional[str] = None + recommended_notes: Optional[str] = None + release_notes: Optional[str] = None + display_platform_properties: Any = None sku_description: str sku_title: str - sku_button_title: Optional[str] - delivery_date_overlay: Any - sku_display_rank: Optional[List] - text_resources: Any - images: Optional[List] - legal_text: Optional[LegalText] + sku_button_title: Optional[str] = None + delivery_date_overlay: Any = None + sku_display_rank: Optional[List] = None + text_resources: Any = None + images: Optional[List] = None + legal_text: Optional[LegalText] = None language: str markets: List[str] class SkuMarketProperty(PascalCaseModel): - first_available_date: Optional[Union[datetime, str]] - supported_languages: Optional[List[str]] - package_ids: Any - pi_filter: Any = Field(alias="PIFilter") + first_available_date: Optional[Union[datetime, str]] = None + supported_languages: Optional[List[str]] = None + package_ids: Any = None + pi_filter: Any = Field(None, alias="PIFilter") markets: List[str] class SkuProperties(PascalCaseModel): - early_adopter_enrollment_url: Any - fulfillment_data: Optional[FulfillmentData] - fulfillment_type: Optional[str] - fulfillment_plugin_id: Any - has_third_party_iaps: Optional[bool] = Field(alias="HasThirdPartyIAPs") - last_update_date: Optional[datetime] - hardware_properties: Optional[HardwareProperties] - hardware_requirements: Optional[List] - hardware_warning_list: Optional[List] + early_adopter_enrollment_url: Any = None + fulfillment_data: Optional[FulfillmentData] = None + fulfillment_type: Optional[str] = None + fulfillment_plugin_id: Any = None + has_third_party_iaps: Optional[bool] = Field(None, alias="HasThirdPartyIAPs") + last_update_date: Optional[datetime] = None + hardware_properties: Optional[HardwareProperties] = None + hardware_requirements: Optional[List] = None + hardware_warning_list: Optional[List] = None installation_terms: str - packages: Optional[List[Package]] - version_string: Optional[str] + packages: Optional[List[Package]] = None + version_string: Optional[str] = None visible_to_b2b_service_ids: List = Field(alias="VisibleToB2BServiceIds") - xbox_xpa: Optional[bool] = Field(alias="XboxXPA") - bundled_skus: Optional[List] + xbox_xpa: Optional[bool] = Field(None, alias="XboxXPA") + bundled_skus: Optional[List] = None is_repurchasable: bool sku_display_rank: int - display_physical_store_inventory: Any + display_physical_store_inventory: Any = None additional_identifiers: List is_trial: bool is_pre_order: bool is_bundle: bool - @validator("last_update_date", pre=True, always=True) - def set_to_none(cls, v): - return v or None - + @field_validator("last_update_date", mode="before", check_fields=True) + def validator(x): + return x or None class Sku(PascalCaseModel): last_modified_date: datetime @@ -262,13 +261,13 @@ class Sku(PascalCaseModel): sku_b_schema: str sku_id: str sku_type: str - recurrence_policy: Any - subscription_policy_id: Any + recurrence_policy: Any = None + subscription_policy_id: Any = None class AllowedPlatform(PascalCaseModel): - max_version: Optional[int] - min_version: Optional[int] + max_version: Optional[int] = None + min_version: Optional[int] = None platform_name: str @@ -298,13 +297,13 @@ class Price(PascalCaseModel): class OrderManagementData(PascalCaseModel): - granted_entitlement_keys: Optional[List] + granted_entitlement_keys: Optional[List] = None pi_filter: Optional[PIFilter] = Field(None, alias="PIFilter") price: Price class AvailabilityProperties(PascalCaseModel): - original_release_date: Optional[datetime] + original_release_date: Optional[datetime] = None class SatisfyingEntitlementKey(PascalCaseModel): @@ -318,99 +317,99 @@ class LicensingData(PascalCaseModel): class Availability(PascalCaseModel): actions: List[str] - availability_a_schema: Optional[str] - availability_b_schema: Optional[str] - availability_id: Optional[str] - conditions: Optional[Conditions] - last_modified_date: Optional[datetime] - markets: Optional[List[str]] - order_management_data: Optional[OrderManagementData] - properties: Optional[AvailabilityProperties] - sku_id: Optional[str] - display_rank: Optional[int] - remediation_required: Optional[bool] - licensing_data: Optional[LicensingData] + availability_a_schema: Optional[str] = None + availability_b_schema: Optional[str] = None + availability_id: Optional[str] = None + conditions: Optional[Conditions] = None + last_modified_date: Optional[datetime] = None + markets: Optional[List[str]] = None + order_management_data: Optional[OrderManagementData] = None + properties: Optional[AvailabilityProperties] = None + sku_id: Optional[str] = None + display_rank: Optional[int] = None + remediation_required: Optional[bool] = None + licensing_data: Optional[LicensingData] = None class DisplaySkuAvailability(PascalCaseModel): - sku: Optional[Sku] + sku: Optional[Sku] = None availabilities: List[Availability] class LocalizedProperty(PascalCaseModel): - developer_name: Optional[str] - display_platform_properties: Optional[Any] - publisher_name: Optional[str] - publisher_website_uri: Optional[str] - support_uri: Optional[str] - eligibility_properties: Optional[Any] - franchises: Optional[List] + developer_name: Optional[str] = None + display_platform_properties: Optional[Any] = None + publisher_name: Optional[str] = None + publisher_website_uri: Optional[str] = None + support_uri: Optional[str] = None + eligibility_properties: Optional[Any] = None + franchises: Optional[List] = None images: List[Image] - videos: Optional[List[Video]] - product_description: Optional[str] + videos: Optional[List[Video]] = None + product_description: Optional[str] = None product_title: str - short_title: Optional[str] - sort_title: Optional[str] - friendly_title: Optional[str] - short_description: Optional[str] - search_titles: Optional[List[SearchTitle]] - voice_title: Optional[str] - render_group_details: Optional[Any] - product_display_ranks: Optional[List] - interactive_model_config: Optional[Any] - interactive_3d_enabled: Optional[bool] = Field(alias="Interactive3DEnabled") - language: Optional[str] - markets: Optional[List[str]] + short_title: Optional[str] = None + sort_title: Optional[str] = None + friendly_title: Optional[str] = None + short_description: Optional[str] = None + search_titles: Optional[List[SearchTitle]] = None + voice_title: Optional[str] = None + render_group_details: Optional[Any] = None + product_display_ranks: Optional[List] = None + interactive_model_config: Optional[Any] = None + interactive_3d_enabled: Optional[bool] = Field(None, alias="Interactive3DEnabled") + language: Optional[str] = None + markets: Optional[List[str]] = None class MarketProperty(PascalCaseModel): - original_release_date: Optional[datetime] - original_release_friendly_name: Optional[str] - minimum_user_age: Optional[int] - content_ratings: Optional[List[ContentRating]] - related_products: Optional[List] + original_release_date: Optional[datetime] = None + original_release_friendly_name: Optional[str] = None + minimum_user_age: Optional[int] = None + content_ratings: Optional[List[ContentRating]] = None + related_products: Optional[List] = None usage_data: List[UsageData] - bundle_config: Optional[Any] - markets: Optional[List[str]] + bundle_config: Optional[Any] = None + markets: Optional[List[str]] = None class Product(PascalCaseModel): - last_modified_date: Optional[datetime] + last_modified_date: Optional[datetime] = None localized_properties: List[LocalizedProperty] market_properties: List[MarketProperty] - product_a_schema: Optional[str] - product_b_schema: Optional[str] + product_a_schema: Optional[str] = None + product_b_schema: Optional[str] = None product_id: str - properties: Optional[ProductProperties] - alternate_ids: Optional[List[AlternateId]] - domain_data_version: Optional[Any] - ingestion_source: Optional[str] - is_microsoft_product: Optional[bool] - preferred_sku_id: Optional[str] - product_type: Optional[str] - validation_data: Optional[ValidationData] - merchandizing_tags: Optional[List] - part_d: Optional[str] + properties: Optional[ProductProperties] = None + alternate_ids: Optional[List[AlternateId]] = None + domain_data_version: Optional[Any] = None + ingestion_source: Optional[str] = None + is_microsoft_product: Optional[bool] = None + preferred_sku_id: Optional[str] = None + product_type: Optional[str] = None + validation_data: Optional[ValidationData] = None + merchandizing_tags: Optional[List] = None + part_d: Optional[str] = None product_family: str - schema_version: Optional[str] + schema_version: Optional[str] = None product_kind: str display_sku_availabilities: List[DisplaySkuAvailability] class CatalogResponse(PascalCaseModel): - big_ids: Optional[List[str]] - has_more_pages: Optional[bool] + big_ids: Optional[List[str]] = None + has_more_pages: Optional[bool] = None products: List[Product] - total_result_count: Optional[int] + total_result_count: Optional[int] = None class SearchProduct(PascalCaseModel): - background_color: Optional[str] - height: Optional[int] - image_type: Optional[str] - width: Optional[int] + background_color: Optional[str] = None + height: Optional[int] = None + image_type: Optional[str] = None + width: Optional[int] = None platform_properties: List - icon: Optional[str] + icon: Optional[str] = None product_id: str type: str title: str diff --git a/xbox/webapi/api/provider/cqs/models.py b/xbox/webapi/api/provider/cqs/models.py index 752336a0..10f9d76a 100644 --- a/xbox/webapi/api/provider/cqs/models.py +++ b/xbox/webapi/api/provider/cqs/models.py @@ -40,7 +40,7 @@ class Program(PascalCaseModel): end_date: str name: str is_repeat: bool - parental_control: Optional[Dict[str, Any]] + parental_control: Optional[Dict[str, Any]] = None genres: List[Genre] category_id: int description: Optional[str] = None diff --git a/xbox/webapi/api/provider/gameclips/models.py b/xbox/webapi/api/provider/gameclips/models.py index 5bbce9fc..bbe3f787 100644 --- a/xbox/webapi/api/provider/gameclips/models.py +++ b/xbox/webapi/api/provider/gameclips/models.py @@ -50,7 +50,7 @@ class GameClip(CamelCaseModel): class PagingInfo(CamelCaseModel): - continuation_token: Optional[str] + continuation_token: Optional[str] = None class GameclipsResponse(CamelCaseModel): diff --git a/xbox/webapi/api/provider/lists/models.py b/xbox/webapi/api/provider/lists/models.py index 7c4b328e..1d05ed4e 100644 --- a/xbox/webapi/api/provider/lists/models.py +++ b/xbox/webapi/api/provider/lists/models.py @@ -6,10 +6,10 @@ class Item(PascalCaseModel): item_id: str content_type: str - title: Optional[str] + title: Optional[str] = None device_type: str - provider: Optional[str] - provider_id: Optional[str] + provider: Optional[str] = None + provider_id: Optional[str] = None class ListItem(PascalCaseModel): diff --git a/xbox/webapi/api/provider/mediahub/models.py b/xbox/webapi/api/provider/mediahub/models.py index bd606540..a8ec3f9d 100644 --- a/xbox/webapi/api/provider/mediahub/models.py +++ b/xbox/webapi/api/provider/mediahub/models.py @@ -6,18 +6,18 @@ class ContentSegment(CamelCaseModel): segment_id: int creation_type: str - creator_channel_id: Optional[str] + creator_channel_id: Optional[str] = None creator_xuid: int record_date: str duration_in_seconds: int offset: int - secondary_title_id: Optional[int] + secondary_title_id: Optional[int] = None title_id: int class ContentLocator(CamelCaseModel): - expiration: Optional[str] - file_size: Optional[int] + expiration: Optional[str] = None + file_size: Optional[int] = None locator_type: str uri: str diff --git a/xbox/webapi/api/provider/message/models.py b/xbox/webapi/api/provider/message/models.py index 9f94f610..cbe8dfb3 100644 --- a/xbox/webapi/api/provider/message/models.py +++ b/xbox/webapi/api/provider/message/models.py @@ -7,9 +7,9 @@ class Part(CamelCaseModel): content_type: str version: int - text: Optional[str] - unsuitable_for: Optional[List] - locator: Optional[str] + text: Optional[str] = None + unsuitable_for: Optional[List] = None + locator: Optional[str] = None class Content(CamelCaseModel): @@ -21,14 +21,14 @@ class ContentPayload(CamelCaseModel): class Message(CamelCaseModel): - content_payload: Optional[ContentPayload] + content_payload: Optional[ContentPayload] = None timestamp: datetime last_update_timestamp: datetime type: str network_id: str conversation_type: str conversation_id: str - owner: Optional[int] + owner: Optional[int] = None sender: str message_id: str is_deleted: bool @@ -79,16 +79,16 @@ class ConversationResponse(CamelCaseModel): network_id: str type: str conversation_id: str - participants: Optional[List[str]] + participants: Optional[List[str]] = None read_horizon: str delete_horizon: str is_read: bool muted: bool folder: str - messages: Optional[List[Message]] - continuation_token: Optional[str] + messages: Optional[List[Message]] = None + continuation_token: Optional[str] = None voice_id: str - voice_roster: Optional[List[Any]] + voice_roster: Optional[List[Any]] = None class SendMessageResponse(CamelCaseModel): diff --git a/xbox/webapi/api/provider/people/models.py b/xbox/webapi/api/provider/people/models.py index 284ef753..822c848b 100644 --- a/xbox/webapi/api/provider/people/models.py +++ b/xbox/webapi/api/provider/people/models.py @@ -31,16 +31,16 @@ class PeopleSummaryResponse(CamelCaseModel): has_caller_marked_target_as_favorite: bool has_caller_marked_target_as_identity_shared: bool legacy_friend_status: str - available_people_slots: Optional[int] - recent_change_count: Optional[int] - watermark: Optional[str] + available_people_slots: Optional[int] = None + recent_change_count: Optional[int] = None + watermark: Optional[str] = None class Suggestion(PascalCaseModel): - type: Optional[str] + type: Optional[str] = None priority: int - reasons: Optional[str] - title_id: Optional[str] + reasons: Optional[str] = None + title_id: Optional[str] = None class Recommendation(PascalCaseModel): @@ -55,18 +55,18 @@ class MultiplayerSummary(PascalCaseModel): class RecentPlayer(CamelCaseModel): titles: List[str] - text: Optional[str] + text: Optional[str] = None class Follower(CamelCaseModel): - text: Optional[str] - followed_date_time: Optional[datetime] + text: Optional[str] = None + followed_date_time: Optional[datetime] = None class PreferredColor(CamelCaseModel): - primary_color: Optional[str] - secondary_color: Optional[str] - tertiary_color: Optional[str] + primary_color: Optional[str] = None + secondary_color: Optional[str] = None + tertiary_color: Optional[str] = None class PresenceDetail(PascalCaseModel): @@ -75,25 +75,25 @@ class PresenceDetail(PascalCaseModel): presence_text: str state: str title_id: str - title_type: Optional[str] + title_type: Optional[str] = None is_primary: bool is_game: bool - rich_presence_text: Optional[str] + rich_presence_text: Optional[str] = None class TitlePresence(PascalCaseModel): is_currently_playing: bool - presence_text: Optional[str] - title_name: Optional[str] - title_id: Optional[str] + presence_text: Optional[str] = None + title_name: Optional[str] = None + title_id: Optional[str] = None class Detail(CamelCaseModel): account_tier: str - bio: Optional[str] + bio: Optional[str] = None is_verified: bool - location: Optional[str] - tenure: Optional[str] + location: Optional[str] = None + tenure: Optional[str] = None watermarks: List[str] blocked: bool mute: bool @@ -108,16 +108,16 @@ class SocialManager(CamelCaseModel): class Avatar(CamelCaseModel): - update_time_offset: Optional[datetime] - spritesheet_metadata: Optional[Any] + update_time_offset: Optional[datetime] = None + spritesheet_metadata: Optional[Any] = None class LinkedAccount(CamelCaseModel): network_name: str - display_name: Optional[str] + display_name: Optional[str] = None show_on_profile: bool is_family_friendly: bool - deeplink: Optional[str] + deeplink: Optional[str] = None class Person(CamelCaseModel): @@ -126,8 +126,8 @@ class Person(CamelCaseModel): is_following_caller: bool is_followed_by_caller: bool is_identity_shared: bool - added_date_time_utc: Optional[datetime] - display_name: Optional[str] + added_date_time_utc: Optional[datetime] = None + display_name: Optional[str] = None real_name: str display_pic_raw: str show_user_as_avatar: str @@ -139,31 +139,31 @@ class Person(CamelCaseModel): xbox_one_rep: str presence_state: str presence_text: str - presence_devices: Optional[Any] + presence_devices: Optional[Any] = None is_broadcasting: bool - is_cloaked: Optional[bool] + is_cloaked: Optional[bool] = None is_quarantined: bool is_xbox_360_gamerpic: bool - last_seen_date_time_utc: Optional[datetime] - suggestion: Optional[Suggestion] - recommendation: Optional[Recommendation] - search: Optional[Any] - titleHistory: Optional[Any] - multiplayer_summary: Optional[MultiplayerSummary] - recent_player: Optional[RecentPlayer] - follower: Optional[Follower] - preferred_color: Optional[PreferredColor] - presence_details: Optional[List[PresenceDetail]] - title_presence: Optional[TitlePresence] - title_summaries: Optional[Any] - presence_title_ids: Optional[List[str]] - detail: Optional[Detail] - community_manager_titles: Optional[Any] - social_manager: Optional[SocialManager] - broadcast: Optional[List[Any]] - tournament_summary: Optional[Any] - avatar: Optional[Avatar] - linked_accounts: Optional[List[LinkedAccount]] + last_seen_date_time_utc: Optional[datetime] = None + suggestion: Optional[Suggestion] = None + recommendation: Optional[Recommendation] = None + search: Optional[Any] = None + titleHistory: Optional[Any] = None + multiplayer_summary: Optional[MultiplayerSummary] = None + recent_player: Optional[RecentPlayer] = None + follower: Optional[Follower] = None + preferred_color: Optional[PreferredColor] = None + presence_details: Optional[List[PresenceDetail]] = None + title_presence: Optional[TitlePresence] = None + title_summaries: Optional[Any] = None + presence_title_ids: Optional[List[str]] = None + detail: Optional[Detail] = None + community_manager_titles: Optional[Any] = None + social_manager: Optional[SocialManager] = None + broadcast: Optional[List[Any]] = None + tournament_summary: Optional[Any] = None + avatar: Optional[Avatar] = None + linked_accounts: Optional[List[LinkedAccount]] = None color_theme: str preferred_flag: str preferred_platforms: List[Any] @@ -204,6 +204,6 @@ class FriendFinderState(CamelCaseModel): class PeopleResponse(CamelCaseModel): people: List[Person] - recommendation_summary: Optional[RecommendationSummary] - friend_finder_state: Optional[FriendFinderState] - account_link_details: Optional[List[LinkedAccount]] + recommendation_summary: Optional[RecommendationSummary] = None + friend_finder_state: Optional[FriendFinderState] = None + account_link_details: Optional[List[LinkedAccount]] = None diff --git a/xbox/webapi/api/provider/presence/__init__.py b/xbox/webapi/api/provider/presence/__init__.py index ef46cfaf..d2dc5260 100644 --- a/xbox/webapi/api/provider/presence/__init__.py +++ b/xbox/webapi/api/provider/presence/__init__.py @@ -70,8 +70,8 @@ async def get_presence_batch( url, json=post_data, headers=self.HEADERS_PRESENCE, **kwargs ) resp.raise_for_status() - parsed = PresenceBatchResponse.parse_obj(resp.json()) - return parsed.__root__ + parsed = PresenceBatchResponse.model_validate(resp.json()) + return parsed.root async def get_presence_own( self, presence_level: PresenceLevel = PresenceLevel.ALL, **kwargs diff --git a/xbox/webapi/api/provider/presence/models.py b/xbox/webapi/api/provider/presence/models.py index 029a925f..aec71887 100644 --- a/xbox/webapi/api/provider/presence/models.py +++ b/xbox/webapi/api/provider/presence/models.py @@ -1,5 +1,6 @@ from enum import Enum from typing import List, Optional +from pydantic import RootModel from xbox.webapi.common.models import CamelCaseModel @@ -18,36 +19,36 @@ class PresenceState(str, Enum): class LastSeen(CamelCaseModel): device_type: str - title_id: Optional[str] + title_id: Optional[str] = None title_name: str timestamp: str class ActivityRecord(CamelCaseModel): - richPresence: Optional[str] - media: Optional[str] + richPresence: Optional[str] = None + media: Optional[str] = None class TitleRecord(CamelCaseModel): - id: Optional[str] - name: Optional[str] - activity: Optional[List[ActivityRecord]] - lastModified: Optional[str] - placement: Optional[str] - state: Optional[str] + id: Optional[str] = None + name: Optional[str] = None + activity: Optional[List[ActivityRecord]] = None + lastModified: Optional[str] = None + placement: Optional[str] = None + state: Optional[str] = None class DeviceRecord(CamelCaseModel): - titles: Optional[List[TitleRecord]] - type: Optional[str] + titles: Optional[List[TitleRecord]] = None + type: Optional[str] = None class PresenceItem(CamelCaseModel): xuid: str state: str - last_seen: Optional[LastSeen] - devices: Optional[List[DeviceRecord]] + last_seen: Optional[LastSeen] = None + devices: Optional[List[DeviceRecord]] = None -class PresenceBatchResponse(CamelCaseModel): - __root__: List[PresenceItem] +class PresenceBatchResponse(RootModel[List[PresenceItem]], CamelCaseModel): + root: List[PresenceItem] diff --git a/xbox/webapi/api/provider/screenshots/models.py b/xbox/webapi/api/provider/screenshots/models.py index b636d67f..e4b73994 100644 --- a/xbox/webapi/api/provider/screenshots/models.py +++ b/xbox/webapi/api/provider/screenshots/models.py @@ -36,7 +36,7 @@ class Screenshot(CamelCaseModel): system_properties: str saved_by_user: bool achievement_id: str - greatest_moment_id: Any + greatest_moment_id: Any = None thumbnails: List[Thumbnail] screenshot_uris: List[ScreenshotUri] xuid: str @@ -48,7 +48,7 @@ class Screenshot(CamelCaseModel): class PagingInfo(CamelCaseModel): - continuation_token: Optional[str] + continuation_token: Optional[str] = None class ScreenshotResponse(CamelCaseModel): diff --git a/xbox/webapi/api/provider/smartglass/models.py b/xbox/webapi/api/provider/smartglass/models.py index a99369fb..1565aecd 100644 --- a/xbox/webapi/api/provider/smartglass/models.py +++ b/xbox/webapi/api/provider/smartglass/models.py @@ -50,7 +50,7 @@ class OpStatus(str, Enum): class SmartglassApiStatus(CamelCaseModel): error_code: str - error_message: Optional[str] + error_message: Optional[str] = None class StorageDevice(CamelCaseModel): @@ -69,11 +69,11 @@ class SmartglassConsole(CamelCaseModel): console_streaming_enabled: bool digital_assistant_remote_control_enabled: bool remote_management_enabled: bool - storage_devices: Optional[List[StorageDevice]] + storage_devices: Optional[List[StorageDevice]] = None class SmartglassConsoleList(CamelCaseModel): - agent_user_id: Optional[str] + agent_user_id: Optional[str] = None result: List[SmartglassConsole] status: SmartglassApiStatus @@ -85,36 +85,36 @@ class SmartglassConsoleStatus(CamelCaseModel): remote_management_enabled: bool focus_app_aumid: str is_tv_configured: bool - login_state: Optional[str] + login_state: Optional[str] = None playback_state: PlaybackState power_state: PowerState - storage_devices: Optional[List[StorageDevice]] + storage_devices: Optional[List[StorageDevice]] = None status: SmartglassApiStatus class InstalledPackage(CamelCaseModel): - one_store_product_id: Optional[str] + one_store_product_id: Optional[str] = None title_id: int - aumid: Optional[str] - last_active_time: Optional[datetime] + aumid: Optional[str] = None + last_active_time: Optional[datetime] = None is_game: bool - name: Optional[str] + name: Optional[str] = None content_type: str instance_id: str storage_device_id: str unique_id: str - legacy_product_id: Optional[str] + legacy_product_id: Optional[str] = None version: int size_in_bytes: int install_time: datetime - update_time: Optional[datetime] - parent_id: Optional[str] + update_time: Optional[datetime] = None + parent_id: Optional[str] = None class InstalledPackagesList(CamelCaseModel): result: List[InstalledPackage] status: SmartglassApiStatus - agent_user_id: Optional[str] + agent_user_id: Optional[str] = None class StorageDevicesList(CamelCaseModel): @@ -129,10 +129,10 @@ class OpStatusNode(CamelCaseModel): originating_session_id: str command: str succeeded: bool - console_status_code: Optional[int] - xccs_error_code: Optional[ErrorCode] - h_result: Optional[int] - message: Optional[str] + console_status_code: Optional[int] = None + xccs_error_code: Optional[ErrorCode] = None + h_result: Optional[int] = None + message: Optional[str] = None class OperationStatusResponse(CamelCaseModel): @@ -147,15 +147,15 @@ class CommandDestination(CamelCaseModel): remote_management_enabled: bool console_streaming_enabled: bool console_type: ConsoleType - wireless_warning: Optional[str] - out_of_home_warning: Optional[str] + wireless_warning: Optional[str] = None + out_of_home_warning: Optional[str] = None class CommandResponse(CamelCaseModel): - result: Optional[str] - ui_text: Optional[str] + result: Optional[str] = None + ui_text: Optional[str] = None destination: CommandDestination - user_info: Optional[str] + user_info: Optional[str] = None op_id: str status: SmartglassApiStatus diff --git a/xbox/webapi/api/provider/titlehub/models.py b/xbox/webapi/api/provider/titlehub/models.py index 4160f119..c4d16633 100644 --- a/xbox/webapi/api/provider/titlehub/models.py +++ b/xbox/webapi/api/provider/titlehub/models.py @@ -47,9 +47,9 @@ class TitleHistory(CamelCaseModel): class Attribute(CamelCaseModel): - applicable_platforms: Optional[List[str]] - maximum: Optional[int] - minimum: Optional[int] + applicable_platforms: Optional[List[str]] = None + maximum: Optional[int] = None + minimum: Optional[int] = None name: str @@ -66,41 +66,41 @@ class Detail(CamelCaseModel): capabilities: List[str] description: str developer_name: str - genres: Optional[List[str]] + genres: Optional[List[str]] = None publisher_name: str min_age: int - release_date: Optional[datetime] - short_description: Optional[str] - vui_display_name: Optional[str] + release_date: Optional[datetime] = None + short_description: Optional[str] = None + vui_display_name: Optional[str] = None xbox_live_gold_required: bool class Title(CamelCaseModel): title_id: str - pfn: Optional[str] - bing_id: Optional[str] - service_config_id: Optional[str] - windows_phone_product_id: Optional[str] + pfn: Optional[str] = None + bing_id: Optional[str] = None + service_config_id: Optional[str] = None + windows_phone_product_id: Optional[str] = None name: str type: str devices: List[str] display_image: str media_item_type: str - modern_title_id: Optional[str] + modern_title_id: Optional[str] = None is_bundle: bool - achievement: Optional[Achievement] - stats: Optional[Stats] - game_pass: Optional[GamePass] - images: Optional[List[Image]] - title_history: Optional[TitleHistory] - detail: Optional[Detail] - friends_who_played: Any - alternate_title_ids: Any - content_boards: Any - xbox_live_tier: Optional[str] - is_streamable: Optional[bool] + achievement: Optional[Achievement] = None + stats: Optional[Stats] = None + game_pass: Optional[GamePass] = None + images: Optional[List[Image]] = None + title_history: Optional[TitleHistory] = None + detail: Optional[Detail] = None + friends_who_played: Any = None + alternate_title_ids: Any = None + content_boards: Any = None + xbox_live_tier: Optional[str] = None + is_streamable: Optional[bool] = None class TitleHubResponse(CamelCaseModel): - xuid: Optional[str] + xuid: Optional[str] = None titles: List[Title] diff --git a/xbox/webapi/api/provider/userstats/models.py b/xbox/webapi/api/provider/userstats/models.py index 0c262c3f..744be54a 100644 --- a/xbox/webapi/api/provider/userstats/models.py +++ b/xbox/webapi/api/provider/userstats/models.py @@ -8,19 +8,19 @@ class GeneralStatsField: class GroupProperties(PascalCaseModel): - ordinal: Optional[str] - sort_order: Optional[str] - display_name: Optional[str] - display_format: Optional[str] - display_semantic: Optional[str] + ordinal: Optional[str] = None + sort_order: Optional[str] = None + display_name: Optional[str] = None + display_format: Optional[str] = None + display_semantic: Optional[str] = None class Properties(PascalCaseModel): - display_name: Optional[str] + display_name: Optional[str] = None class Stat(LowerCaseModel): - group_properties: Optional[GroupProperties] + group_properties: Optional[GroupProperties] = None xuid: str scid: str name: str @@ -37,10 +37,10 @@ class StatListsCollectionItem(LowerCaseModel): class Group(LowerCaseModel): name: str - title_id: Optional[str] + title_id: Optional[str] = None statlistscollection: List[StatListsCollectionItem] class UserStatsResponse(LowerCaseModel): - groups: Optional[List[Group]] + groups: Optional[List[Group]] = None statlistscollection: List[StatListsCollectionItem] diff --git a/xbox/webapi/authentication/models.py b/xbox/webapi/authentication/models.py index 38fd5fed..45578376 100644 --- a/xbox/webapi/authentication/models.py +++ b/xbox/webapi/authentication/models.py @@ -87,7 +87,7 @@ class OAuth2TokenResponse(BaseModel): expires_in: int scope: str access_token: str - refresh_token: Optional[str] + refresh_token: Optional[str] = None user_id: str issued: datetime = Field(default_factory=utc_now) @@ -125,7 +125,7 @@ class SisuAuthorizationResponse(PascalCaseModel): authorization_token: XSTSResponse web_page: str sandbox: str - use_modern_gamertag: Optional[bool] + use_modern_gamertag: Optional[bool] = None """Signature related models""" @@ -135,12 +135,12 @@ class TitleEndpoint(PascalCaseModel): protocol: str host: str host_type: str - path: Optional[str] - relying_party: Optional[str] - sub_relying_party: Optional[str] - token_type: Optional[str] - signature_policy_index: Optional[int] - server_cert_index: Optional[List[int]] + path: Optional[str] = None + relying_party: Optional[str] = None + sub_relying_party: Optional[str] = None + token_type: Optional[str] = None + signature_policy_index: Optional[int] = None + server_cert_index: Optional[List[int]] = None class SignaturePolicy(PascalCaseModel): @@ -151,7 +151,7 @@ class SignaturePolicy(PascalCaseModel): class TitleEndpointCertificate(PascalCaseModel): thumbprint: str - is_issuer: Optional[bool] + is_issuer: Optional[bool] = None root_cert_index: int diff --git a/xbox/webapi/authentication/xal.py b/xbox/webapi/authentication/xal.py index d2793299..ffb05c22 100644 --- a/xbox/webapi/authentication/xal.py +++ b/xbox/webapi/authentication/xal.py @@ -227,7 +227,7 @@ async def request_sisu_authentication( ) resp.raise_for_status() return ( - SisuAuthenticationResponse.parse_raw(resp.content), + SisuAuthenticationResponse.model_validate_json(resp.content), resp.headers["X-SessionId"], ) diff --git a/xbox/webapi/common/filetimes.py b/xbox/webapi/common/filetimes.py index 1f0cd625..7030e12e 100644 --- a/xbox/webapi/common/filetimes.py +++ b/xbox/webapi/common/filetimes.py @@ -25,7 +25,7 @@ """Tools to convert between Python datetime instances and Microsoft times. """ from calendar import timegm -from datetime import datetime, timedelta, tzinfo +import datetime # http://support.microsoft.com/kb/167296 # How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME @@ -33,11 +33,11 @@ HUNDREDS_OF_NANOSECONDS = 10000000 -ZERO = timedelta(0) -HOUR = timedelta(hours=1) +ZERO = datetime.timedelta(0) +HOUR = datetime.timedelta(hours=1) -class UTC(tzinfo): +class UTC(datetime.tzinfo): """UTC""" def utcoffset(self, dt): @@ -53,7 +53,7 @@ def dst(self, dt): utc = UTC() -def dt_to_filetime(dt): +def dt_to_filetime(dt: datetime.datetime): """Converts a datetime to Microsoft filetime format. If the object is time zone-naive, it is forced to UTC before conversion. @@ -75,7 +75,7 @@ def dt_to_filetime(dt): return ft + (dt.microsecond * 10) -def filetime_to_dt(ft): +def filetime_to_dt(ft: int): """Converts a Microsoft filetime number to a Python datetime. The new datetime object is time zone-naive but is equivalent to tzinfo=utc. @@ -91,7 +91,7 @@ def filetime_to_dt(ft): # Get seconds and remainder in terms of Unix epoch (s, ns100) = divmod(ft - EPOCH_AS_FILETIME, HUNDREDS_OF_NANOSECONDS) # Convert to datetime object - dt = datetime.utcfromtimestamp(s) + dt = datetime.datetime.fromtimestamp(s, datetime.UTC) # Add remainder in as microseconds. Python 3.2 requires an integer dt = dt.replace(microsecond=(ns100 // 10)) return dt diff --git a/xbox/webapi/common/models.py b/xbox/webapi/common/models.py index 22a74318..d2902df8 100644 --- a/xbox/webapi/common/models.py +++ b/xbox/webapi/common/models.py @@ -1,5 +1,5 @@ """Base Models.""" -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel def to_pascal(string): @@ -16,21 +16,12 @@ def to_lower(string): class PascalCaseModel(BaseModel): - class Config: - arbitrary_types_allowed = True - allow_population_by_field_name = True - alias_generator = to_pascal + model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, alias_generator=to_pascal) class CamelCaseModel(BaseModel): - class Config: - arbitrary_types_allowed = True - allow_population_by_field_name = True - alias_generator = to_camel + model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, alias_generator=to_camel) class LowerCaseModel(BaseModel): - class Config: - arbitrary_types_allowed = True - allow_population_by_field_name = True - alias_generator = to_lower + model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, alias_generator=to_lower) diff --git a/xbox/webapi/common/request_signer.py b/xbox/webapi/common/request_signer.py index b3035fe6..e556235a 100644 --- a/xbox/webapi/common/request_signer.py +++ b/xbox/webapi/common/request_signer.py @@ -4,7 +4,7 @@ Employed for generating the "Signature" header in authentication requests. """ import base64 -from datetime import datetime +from datetime import datetime, timezone import hashlib import struct from typing import Optional @@ -101,7 +101,7 @@ def sign( timestamp: datetime = None, ) -> str: if timestamp is None: - timestamp = datetime.utcnow() + timestamp = datetime.now(timezone.utc) signature = self._sign_raw( method, path_and_query, body, authorization, timestamp diff --git a/xbox/webapi/scripts/authenticate.py b/xbox/webapi/scripts/authenticate.py index fb917b6b..58f67cd3 100644 --- a/xbox/webapi/scripts/authenticate.py +++ b/xbox/webapi/scripts/authenticate.py @@ -82,7 +82,7 @@ async def do_auth( if os.path.exists(token_filepath): with open(token_filepath) as f: tokens = f.read() - auth_mgr.oauth = OAuth2TokenResponse.parse_raw(tokens) + auth_mgr.oauth = OAuth2TokenResponse.model_validate_json(tokens) await auth_mgr.refresh_tokens() # Request new ones if they are not valid diff --git a/xbox/webapi/scripts/change_gamertag.py b/xbox/webapi/scripts/change_gamertag.py index 09c36695..ce10d498 100644 --- a/xbox/webapi/scripts/change_gamertag.py +++ b/xbox/webapi/scripts/change_gamertag.py @@ -58,7 +58,7 @@ async def async_main(): with open(args.tokens) as f: tokens = f.read() - auth_mgr.oauth = OAuth2TokenResponse.parse_raw(tokens) + auth_mgr.oauth = OAuth2TokenResponse.model_validate_json(tokens) try: await auth_mgr.refresh_tokens() except HTTPStatusError: diff --git a/xbox/webapi/scripts/friends.py b/xbox/webapi/scripts/friends.py index 70267a20..d5c5ed67 100644 --- a/xbox/webapi/scripts/friends.py +++ b/xbox/webapi/scripts/friends.py @@ -50,7 +50,7 @@ async def async_main(): with open(args.tokens) as f: tokens = f.read() - auth_mgr.oauth = OAuth2TokenResponse.parse_raw(tokens) + auth_mgr.oauth = OAuth2TokenResponse.model_validate_json(tokens) try: await auth_mgr.refresh_tokens() except HTTPStatusError: From 23d92f6ee099f6e27f29f9063ae002415f501f39 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:08:34 +0100 Subject: [PATCH 02/13] ci: Include py3.12 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b33b76e..6a2a52c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 @@ -40,7 +40,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.11' + python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip From d1fdfe1385af1852499ae78b85a6d7dcf3de60d5 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:22:32 +0100 Subject: [PATCH 03/13] meta: Migrate setup.py to pyproject.toml --- pyproject.toml | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 74 -------------------------------------------------- 2 files changed, 72 insertions(+), 74 deletions(-) delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml index 3dedbdb0..d0b2763f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,75 @@ +[project] +name = "xbox-webapi" +version = "2.0.11" +description = "A library to authenticate with Windows Live/Xbox Live and use their API" +authors = [ + {name = "OpenXbox"}, +] +dependencies = [ + "appdirs", + "ecdsa", + "httpx", + "ms_cv", + "pydantic", +] +requires-python = ">=3.8" +readme = "README.md" +license = {text = "GPL"} +keywords = ["xbox one live api"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Software Development :: Libraries :: Python Modules", +] + +[project.urls] +Homepage = "https://github.com/OpenXbox/xbox-webapi-python" + +[project.optional-dependencies] +dev = [ + "bump2version", + "coverage", + "flake8", + "pip", + "pytest", + "pytest-asyncio", + "pytest-cov", + "pytest-runner", + "respx", + "tox", + "twine", + "watchdog", + "wheel", +] +docs = [ + "Sphinx", + "sphinx-mdinclude", + "sphinx_rtd_theme", +] + +[project.scripts] +xbox-authenticate = "xbox.webapi.scripts.authenticate:main" +xbox-change-gt = "xbox.webapi.scripts.change_gamertag:main" +xbox-friends = "xbox.webapi.scripts.friends:main" +xbox-searchlive = "xbox.webapi.scripts.search:main" +xbox-xal = "xbox.webapi.scripts.xal:main" + [tool.black] target-version = ["py37", "py38", "py39", "py310", "py311"] exclude = 'generated' + +[tool.setuptools.packages.find] +where = ["."] +include = ["xbox.*"] + +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py deleted file mode 100644 index 582b6e95..00000000 --- a/setup.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python - -from setuptools import find_packages, setup - -PACKAGES = [f"xbox.{p}" for p in find_packages(where="xbox")] - -setup( - name="xbox-webapi", - version="2.0.11", - author="OpenXbox", - description="A library to authenticate with Windows Live/Xbox Live and use their API", - long_description=open("README.md").read() + "\n\n" + open("CHANGELOG.md").read(), - long_description_content_type="text/markdown", - license="GPL", - keywords="xbox one live api", - url="https://github.com/OpenXbox/xbox-webapi-python", - packages=PACKAGES, - namespace_packages=["xbox"], - zip_safe=False, - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Topic :: Software Development :: Libraries :: Python Modules", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - ], - test_suite="tests", - install_requires=[ - "httpx", - "appdirs", - "ms_cv", - "pydantic", - "ecdsa", - ], - setup_requires=["pytest-runner"], - tests_require=["pytest", "pytest-cov", "pytest-asyncio", "respx"], - extras_require={ - "dev": [ - "pip", - "bump2version", - "wheel", - "watchdog", - "flake8", - "tox", - "coverage", - # PyPi - "twine", - # Tests - "pytest", - "pytest-cov", - "pytest-asyncio", - "pytest-runner", - ], - "docs": [ - "Sphinx", - "sphinx_rtd_theme", - "sphinx-mdinclude", - ], - }, - entry_points={ - "console_scripts": [ - "xbox-authenticate=xbox.webapi.scripts.authenticate:main", - "xbox-xal=xbox.webapi.scripts.xal:main", - "xbox-searchlive=xbox.webapi.scripts.search:main", - "xbox-change-gt=xbox.webapi.scripts.change_gamertag:main", - "xbox-friends=xbox.webapi.scripts.friends:main", - ] - }, -) From 6d0e4a68d106c0a6363875ad4f904973274e1b9b Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:32:24 +0100 Subject: [PATCH 04/13] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d0b2763f..695ec84e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ exclude = 'generated' [tool.setuptools.packages.find] where = ["."] -include = ["xbox.*"] +include = ["xbox.webapi"] [build-system] requires = ["setuptools>=61", "wheel"] From eb7db6140a11793aa37b52cea44294c36b98b1b1 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:33:47 +0100 Subject: [PATCH 05/13] Update build.yml --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a2a52c4..68c79643 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -e .[dev] - python setup.py develop if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | From 364ac013f87ea5663009070d3f32f7de2fd19877 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:41:31 +0100 Subject: [PATCH 06/13] compat: Use more compatible datetime definitions --- .pre-commit-config.yaml | 54 +++++---------------------------- tests/conftest.py | 8 +++-- xbox/webapi/common/filetimes.py | 20 ++++++------ 3 files changed, 23 insertions(+), 59 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b5778476..4d18dd9f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,47 +1,9 @@ repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 - hooks: - - id: pyupgrade - args: [--py36-plus] - - repo: https://github.com/psf/black - rev: 22.10.0 - hooks: - - id: black - args: - - --safe - - --quiet - files: ^((xbox|tests)/.+)?[^/]+\.py$ - - repo: https://gitlab.com/pycqa/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - additional_dependencies: - # - flake8-docstrings==1.5.0 - - pydocstyle==5.1.1 - files: ^(xbox)/.+\.py$ - - repo: https://github.com/PyCQA/bandit - rev: 1.7.4 - hooks: - - id: bandit - args: - - --quiet - - --format=custom - - --configfile=bandit.yaml - files: ^(xbox|tests)/.+\.py$ - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 - hooks: - - id: check-executables-have-shebangs - stages: [manual] - - id: check-json - - repo: https://github.com/prettier/prettier - rev: 2.0.4 - hooks: - - id: prettier - stages: [manual] - \ No newline at end of file +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.1.6 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format diff --git a/tests/conftest.py b/tests/conftest.py index 560e665b..81f87b82 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -from datetime import datetime, UTC +from datetime import datetime, timezone import uuid from ecdsa.keys import SigningKey, VerifyingKey @@ -29,7 +29,9 @@ async def auth_mgr(event_loop): session = SignedSession() mgr = AuthenticationManager(session, "abc", "123", "http://localhost") - mgr.oauth = OAuth2TokenResponse.model_validate_json(get_response("auth_oauth2_token")) + mgr.oauth = OAuth2TokenResponse.model_validate_json( + get_response("auth_oauth2_token") + ) mgr.user_token = XAUResponse.model_validate_json(get_response("auth_user_token")) mgr.xsts_token = XSTSResponse.model_validate_json(get_response("auth_xsts_token")) yield mgr @@ -77,4 +79,4 @@ def synthetic_request_signer(ecdsa_signing_key) -> RequestSigner: @pytest.fixture(scope="session") def synthetic_timestamp() -> datetime: - return datetime.fromtimestamp(1586999965, UTC) + return datetime.fromtimestamp(1586999965, timezone.utc) diff --git a/xbox/webapi/common/filetimes.py b/xbox/webapi/common/filetimes.py index 7030e12e..3dd70107 100644 --- a/xbox/webapi/common/filetimes.py +++ b/xbox/webapi/common/filetimes.py @@ -25,7 +25,7 @@ """Tools to convert between Python datetime instances and Microsoft times. """ from calendar import timegm -import datetime +from datetime import datetime, timezone, tzinfo, timedelta # http://support.microsoft.com/kb/167296 # How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME @@ -33,27 +33,27 @@ HUNDREDS_OF_NANOSECONDS = 10000000 -ZERO = datetime.timedelta(0) -HOUR = datetime.timedelta(hours=1) +ZERO = timedelta(0) +HOUR = timedelta(hours=1) -class UTC(datetime.tzinfo): +class UTC(tzinfo): """UTC""" - def utcoffset(self, dt): + def utcoffset(self, dt: datetime) -> timedelta: return ZERO - def tzname(self, dt): + def tzname(self, dt: datetime) -> str: return "UTC" - def dst(self, dt): + def dst(self, dt: datetime) -> timedelta: return ZERO utc = UTC() -def dt_to_filetime(dt: datetime.datetime): +def dt_to_filetime(dt: datetime) -> int: """Converts a datetime to Microsoft filetime format. If the object is time zone-naive, it is forced to UTC before conversion. @@ -75,7 +75,7 @@ def dt_to_filetime(dt: datetime.datetime): return ft + (dt.microsecond * 10) -def filetime_to_dt(ft: int): +def filetime_to_dt(ft: int) -> datetime: """Converts a Microsoft filetime number to a Python datetime. The new datetime object is time zone-naive but is equivalent to tzinfo=utc. @@ -91,7 +91,7 @@ def filetime_to_dt(ft: int): # Get seconds and remainder in terms of Unix epoch (s, ns100) = divmod(ft - EPOCH_AS_FILETIME, HUNDREDS_OF_NANOSECONDS) # Convert to datetime object - dt = datetime.datetime.fromtimestamp(s, datetime.UTC) + dt = datetime.fromtimestamp(s, timezone.utc) # Add remainder in as microseconds. Python 3.2 requires an integer dt = dt.replace(microsecond=(ns100 // 10)) return dt From b84f6baf7073ccd0ea0b3ad9176fa930482324e2 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:32:59 +0100 Subject: [PATCH 07/13] meta: cleanup, bump dependencies --- .github/workflows/build.yml | 21 ++++----- .pre-commit-config.yaml | 33 +++++++++++++ .readthedocs.yml | 4 +- Dockerfile | 4 +- Makefile | 8 +--- bandit.yaml | 17 ------- pyproject.toml | 94 +++++++++++++++++++++++++++++++++---- requirements.txt | 23 --------- tests/test_xal.py | 2 - 9 files changed, 134 insertions(+), 72 deletions(-) delete mode 100644 bandit.yaml delete mode 100644 requirements.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68c79643..ad7c0f93 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,22 +11,19 @@ jobs: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e .[dev] - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 + - name: Lint with ruff run: | - # stop the build if there are Python syntax errors or undefined names - flake8 xbox --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 xbox --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + ruff check xbox + ruff check tests - name: Test with pytest run: | pytest @@ -35,20 +32,20 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install setuptools wheel twine build - name: Build and publish if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_KEY }} run: | - python setup.py sdist bdist_wheel + python -m build twine upload dist/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d18dd9f..68b1236f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,5 +5,38 @@ repos: hooks: # Run the linter. - id: ruff + args: [ --fix ] # Run the formatter. - id: ruff-format +- repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: [--py38-plus] +- repo: https://github.com/psf/black + rev: 23.11.0 + hooks: + - id: black + args: + - --safe + - --quiet + files: ^((xbox|tests)/.+)?[^/]+\.py$ +- repo: https://github.com/PyCQA/bandit + rev: 1.7.5 + hooks: + - id: bandit + args: + - --configfile=pyproject.toml + - --quiet + - --format=custom + files: ^(xbox|tests)/.+\.py$ +- repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-executables-have-shebangs + stages: [manual] + - id: check-json \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml index 45f34ff5..8a14e9b4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,9 +9,9 @@ sphinx: build: # readdocs master now includes a rust toolchain - os: "ubuntu-20.04" + os: "ubuntu-22.04" tools: - python: "3.9" + python: "3.12" python: install: diff --git a/Dockerfile b/Dockerfile index e8c26353..1e639471 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ######################################### # Image WITH C compiler, building wheels for next stage -FROM python:3.10-alpine as bigimage +FROM python:3.12-alpine as bigimage ENV LANG C.UTF-8 @@ -17,7 +17,7 @@ RUN pip wheel --wheel-dir=/root/wheels /src/xbox-webapi ######################################### # Image WITHOUT C compiler, installing the component from wheel -FROM python:3.10-alpine as smallimage +FROM python:3.12-alpine as smallimage RUN apk add --no-cache openssl diff --git a/Makefile b/Makefile index 2f295a73..26cafdc4 100644 --- a/Makefile +++ b/Makefile @@ -45,19 +45,16 @@ clean-pyc: ## remove Python file artifacts find . -name '__pycache__' -exec rm -fr {} + clean-test: ## remove test and coverage artifacts - rm -fr .tox/ rm -f .coverage rm -fr htmlcov/ lint: ## check style with flake8 - flake8 xbox tests + ruff check --fix xbox + ruff check --fix tests test: ## run tests quickly with the default Python py.test -test-all: ## run tests on every Python version with tox - tox - coverage: ## check code coverage quickly with the default Python coverage run --source xbox -m pytest coverage report -m @@ -84,6 +81,5 @@ dist: clean ## builds source and wheel package ls -l dist install: clean ## install the package to the active Python's site-packages - pip install -r requirements.txt pre-commit install pip install -e . diff --git a/bandit.yaml b/bandit.yaml deleted file mode 100644 index ebd284ea..00000000 --- a/bandit.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# https://bandit.readthedocs.io/en/latest/config.html - -tests: - - B108 - - B306 - - B307 - - B313 - - B314 - - B315 - - B316 - - B317 - - B318 - - B319 - - B320 - - B325 - - B602 - - B604 diff --git a/pyproject.toml b/pyproject.toml index 695ec84e..a48d8929 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ dependencies = [ "ecdsa", "httpx", "ms_cv", - "pydantic", + "pydantic==2.*", ] requires-python = ">=3.8" readme = "README.md" @@ -21,12 +21,11 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", ] @@ -35,19 +34,28 @@ Homepage = "https://github.com/OpenXbox/xbox-webapi-python" [project.optional-dependencies] dev = [ +# Tagging releases "bump2version", +# Testing "coverage", - "flake8", - "pip", "pytest", "pytest-asyncio", "pytest-cov", "pytest-runner", "respx", - "tox", +# Packaging "twine", "watchdog", "wheel", +# Pre-commit / Linting + "ruff==0.1.6", + "pre-commit==3.5.0", + "pyupgrade==3.15.0", + "black==23.11.0", +# "flake8-docstrings==1.5.0", + "pydocstyle==6.1.1", + "bandit==1.7.5", + "isort==5.12.0", ] docs = [ "Sphinx", @@ -62,13 +70,83 @@ xbox-friends = "xbox.webapi.scripts.friends:main" xbox-searchlive = "xbox.webapi.scripts.search:main" xbox-xal = "xbox.webapi.scripts.xal:main" +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", +] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.8 +target-version = "py38" + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F"] +ignore = [] + +fixable = ["ALL"] +unfixable = [] +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +[tool.bandit] +exclude_dirs = ["tests"] +tests = [ + "B108", + "B306", + "B307", + "B313", + "B314", + "B315", + "B316", + "B317", + "B318", + "B319", + "B320", + "B602", + "B604" +] + [tool.black] target-version = ["py37", "py38", "py39", "py310", "py311"] exclude = 'generated' [tool.setuptools.packages.find] where = ["."] -include = ["xbox.webapi"] +include = ["xbox.webapi.*"] [build-system] requires = ["setuptools>=61", "wheel"] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e5ce1b2a..00000000 --- a/requirements.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Run -httpx -appdirs -ecdsa -ms_cv -pydantic==2.* - -# Dev -pytest -pytest-cov -pytest-asyncio -pylint -respx - -# pre-commit -pre-commit==2.20.0 -pyupgrade==3.2.0 -black==22.10.0 -flake8==3.9.2 -# flake8-docstrings==1.5.0 -pydocstyle==6.1.1 -bandit==1.7.4 -isort==5.10.1 diff --git a/tests/test_xal.py b/tests/test_xal.py index 5b7c7670..23559c4b 100644 --- a/tests/test_xal.py +++ b/tests/test_xal.py @@ -1,5 +1,3 @@ -import uuid - from httpx import AsyncClient, Response import pytest From ec5d03531a8e4a14fab2bbcb7c5706b3435d037e Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:34:45 +0100 Subject: [PATCH 08/13] ci: oops --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad7c0f93..b814deb4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,8 +22,8 @@ jobs: pip install -e .[dev] - name: Lint with ruff run: | - ruff check xbox - ruff check tests + ruff check xbox + ruff check tests - name: Test with pytest run: | pytest From 069487db9b9f61628b8c4b4ea044557644159802 Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:36:10 +0100 Subject: [PATCH 09/13] Bump minimum required python version to v3.8 --- .github/workflows/build.yml | 2 +- CONTRIBUTING.md | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b814deb4..1af700bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a35ce62c..db7edd6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,6 +98,6 @@ Before you submit a pull request, check that it meets these guidelines: 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.md. -3. The pull request should work for Python 3.7+. Check +3. The pull request should work for Python 3.8+. Check and make sure that the tests pass for all supported Python versions. diff --git a/README.md b/README.md index e425ceb4..9d09016d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Authentication is supported via OAuth2. ## Dependencies -- Python >= 3.7 +- Python >= 3.8 ## How to use From ebffb4e539cdd2209a3ff881a6611bac7bfa877d Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:41:21 +0100 Subject: [PATCH 10/13] More adjustments to project metadata --- CONTRIBUTING.md | 2 +- Makefile | 5 ++--- pyproject.toml | 1 + setup.cfg | 16 +++------------- tests/conftest.py | 2 -- 5 files changed, 7 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db7edd6b..e946a411 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ cd xbox-webapi-python python -m venv venv source venv/bin/activate pip install -e .[dev] -python setup.py develop +pre-commit install ``` 4. Create a branch for local development:: diff --git a/Makefile b/Makefile index 26cafdc4..b678e2a5 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ clean-test: ## remove test and coverage artifacts rm -f .coverage rm -fr htmlcov/ -lint: ## check style with flake8 +lint: ## check style with ruff ruff check --fix xbox ruff check --fix tests @@ -76,8 +76,7 @@ release: clean ## package and upload a release twine upload dist/* dist: clean ## builds source and wheel package - python setup.py sdist - python setup.py bdist_wheel + python -m build ls -l dist install: clean ## install the package to the active Python's site-packages diff --git a/pyproject.toml b/pyproject.toml index a48d8929..8dde48f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,6 +91,7 @@ exclude = [ "_build", "buck-out", "build", + "docs", "dist", "node_modules", "venv", diff --git a/setup.cfg b/setup.cfg index 26e784c2..9cd6e18f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,9 +3,9 @@ current_version = 2.0.11 commit = True tag = True -[bumpversion:file:setup.py] -search = version="{current_version}" -replace = version="{new_version}" +[bumpversion:file:pyproject.toml] +search = version = "{current_version}" +replace = version = "{new_version}" [bumpversion:file:xbox/webapi/__init__.py] search = __version__ = "{current_version}" @@ -18,16 +18,6 @@ replace = release = '{new_version}' [bdist_wheel] universal = 1 -[flake8] -exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build,docs -max-line-length = 88 -ignore = - E501, - W503, - E203, - D202, - W504 - [isort] profile = black force_sort_within_sections = true diff --git a/tests/conftest.py b/tests/conftest.py index 81f87b82..499bd862 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,8 +22,6 @@ from tests.common import get_response -collect_ignore = ["setup.py"] - @pytest_asyncio.fixture(scope="function") async def auth_mgr(event_loop): From 9dac8d301fbf75f3f1058bb5c17c1c891d7949ea Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:49:57 +0100 Subject: [PATCH 11/13] Update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d77ebc68..a150074d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2.1.0 (2023-11-25) +* ! Deprecate python 3.7 ! +* Deprecate flake8, use ruff for linting instead +* Fixes for compatibility with v2 of json parsing library `pydantic` +* Use python3.12 for CI and Docker +* Migrate from setup.py to pyproject.toml +* Remove requirements.txt +* Use datetime functionality with higher compatibility + ## 2.0.11 (2021-04-30) * Model validation fixes for optional properties (#41) From dd0f6e41018c0f024a1f5a56f54ac15c1a60643b Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:50:07 +0100 Subject: [PATCH 12/13] =?UTF-8?q?Bump=20version:=202.0.11=20=E2=86=92=202.?= =?UTF-8?q?1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- xbox/webapi/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 285e20cf..afa77b2c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ # The short X.Y version version = '1.0' # The full version, including alpha/beta/rc tags -release = '2.0.11' +release = '2.1.0' # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 8dde48f2..dc2d9e56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "xbox-webapi" -version = "2.0.11" +version = "2.1.0" description = "A library to authenticate with Windows Live/Xbox Live and use their API" authors = [ {name = "OpenXbox"}, diff --git a/setup.cfg b/setup.cfg index 9cd6e18f..eda03def 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.0.11 +current_version = 2.1.0 commit = True tag = True diff --git a/xbox/webapi/__init__.py b/xbox/webapi/__init__.py index 736daeb3..71635de6 100644 --- a/xbox/webapi/__init__.py +++ b/xbox/webapi/__init__.py @@ -1,4 +1,4 @@ """Top-level package for xbox-webapi-python.""" __author__ = """OpenXbox""" -__version__ = "2.0.11" +__version__ = "2.1.0" From 8fe0f8bda8b781f6f579ec0b45c73e3ab98aa4db Mon Sep 17 00:00:00 2001 From: tuxuser <462620+tuxuser@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:54:51 +0100 Subject: [PATCH 13/13] docker: Add cargo as build dependency --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1e639471..132d30a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ ENV LANG C.UTF-8 COPY . /src/xbox-webapi # install the C compiler -RUN apk add --no-cache jq gcc musl-dev libffi-dev openssl-dev +RUN apk add --no-cache jq gcc musl-dev libffi-dev openssl-dev cargo # instead of installing, create a wheel RUN pip wheel --wheel-dir=/root/wheels /src/xbox-webapi